diff options
Diffstat (limited to 'src/de/danoeh/antennapod/asynctask')
3 files changed, 491 insertions, 101 deletions
diff --git a/src/de/danoeh/antennapod/asynctask/DownloadObserver.java b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java new file mode 100644 index 000000000..26e405615 --- /dev/null +++ b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java @@ -0,0 +1,150 @@ +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 de.danoeh.antennapod.AppConfig; +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 = 1000; + + private final 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) { + if (activity == null) throw new IllegalArgumentException("activity = null"); + if (handler == null) throw new IllegalArgumentException("handler = null"); + if (callback == null) throw new IllegalArgumentException("callback = null"); + + this.activity = activity; + this.handler = handler; + this.callback = callback; + } + + public void onResume() { + if (AppConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed"); + activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED)); + activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0); + } + + public void onPause() { + if (AppConfig.DEBUG) Log.d(TAG, "DownloadObserver paused"); + activity.unregisterReceiver(contentChangedReceiver); + activity.unbindService(mConnection); + stopRefresher(); + } + + private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + callback.onContentChanged(); + startRefresher(); + } + }; + + public interface Callback { + void onContentChanged(); + + void onDownloadDataAvailable(List<Downloader> downloaderList); + } + + 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 (AppConfig.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(); + List<Downloader> downloaderList = downloadService.getDownloads(); + if (downloaderList == null || downloaderList.isEmpty()) { + Thread.currentThread().interrupt(); + } + } + }); + } + } + +} diff --git a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java index 975aa5efe..3034bbaff 100644 --- a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java +++ b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java @@ -1,115 +1,308 @@ package de.danoeh.antennapod.asynctask; -import org.shredzone.flattr4j.exception.FlattrException; - import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; import android.app.ProgressDialog; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.AsyncTask; +import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.util.flattr.FlattrThing; import de.danoeh.antennapod.util.flattr.FlattrUtils; -/** Performs a click action in a background thread. */ - -public class FlattrClickWorker extends AsyncTask<Void, Void, Void> { - protected static final String TAG = "FlattrClickWorker"; - protected Context context; - protected String url; - protected String errorMsg; - protected int exitCode; - protected ProgressDialog progDialog; - - protected final static int SUCCESS = 0; - protected final static int NO_TOKEN = 1; - protected final static int FLATTR_ERROR = 2; - - public FlattrClickWorker(Context context, String url) { - super(); - this.context = context; - this.url = url; - exitCode = SUCCESS; - errorMsg = ""; - } - - protected void onNoAccessToken() { - Log.w(TAG, "No access token was available"); - if (url.equals(FlattrUtils.APP_URL)) { - FlattrUtils.showNoTokenDialog(context, FlattrUtils.APP_LINK); - } else { - FlattrUtils.showNoTokenDialog(context, url); - } - } - - protected void onFlattrError() { - FlattrUtils.showErrorDialog(context, errorMsg); - } - - protected void onSuccess() { - Toast toast = Toast.makeText(context.getApplicationContext(), - R.string.flattr_click_success, Toast.LENGTH_LONG); - toast.show(); - } - - protected void onSetupProgDialog() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.flattring_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); - } - - @Override - protected void onPostExecute(Void result) { - if (AppConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode); - if (progDialog != null) { - progDialog.dismiss(); - } - switch (exitCode) { - case NO_TOKEN: - onNoAccessToken(); - break; - case FLATTR_ERROR: - onFlattrError(); - break; - case SUCCESS: - onSuccess(); - break; - } - } - - @Override - protected void onPreExecute() { - onSetupProgDialog(); - } - - @Override - protected Void doInBackground(Void... params) { - if (AppConfig.DEBUG) Log.d(TAG, "Starting background work"); - if (FlattrUtils.hasToken()) { - try { - FlattrUtils.clickUrl(context, url); - } catch (FlattrException e) { - e.printStackTrace(); - exitCode = FLATTR_ERROR; - errorMsg = e.getMessage(); - } - } else { - exitCode = NO_TOKEN; - } - return null; - } - - @SuppressLint("NewApi") - public void executeAsync() { - FlattrUtils.hasToken(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } else { - execute(); - } - } +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Performs a click action in a background thread. + */ + +public class FlattrClickWorker extends AsyncTask<Void, String, Void> { + protected static final String TAG = "FlattrClickWorker"; + protected Context context; + + private final int NOTIFICATION_ID = 4; + + protected String errorMsg; + protected int exitCode; + protected ArrayList<String> flattrd; + protected ArrayList<String> flattr_failed; + + + protected NotificationCompat.Builder notificationCompatBuilder; + private Notification.BigTextStyle notificationBuilder; + protected NotificationManager notificationManager; + + protected ProgressDialog progDialog; + + protected final static int EXIT_DEFAULT = 0; + protected final static int NO_TOKEN = 1; + protected final static int ENQUEUED = 2; + protected final static int NO_THINGS = 3; + + public final static int ENQUEUE_ONLY = 1; + public final static int FLATTR_TOAST = 2; + public static final int FLATTR_NOTIFICATION = 3; + + private int run_mode = FLATTR_NOTIFICATION; + + private FlattrThing extra_flattr_thing; // additional urls to flattr that do *not* originate from the queue + + /** + * @param context + * @param run_mode can be one of ENQUEUE_ONLY, FLATTR_TOAST and FLATTR_NOTIFICATION + */ + public FlattrClickWorker(Context context, int run_mode) { + this(context); + this.run_mode = run_mode; + } + + public FlattrClickWorker(Context context) { + super(); + this.context = context; + exitCode = EXIT_DEFAULT; + + flattrd = new ArrayList<String>(); + flattr_failed = new ArrayList<String>(); + + errorMsg = ""; + } + + /* only used in PreferencesActivity for flattring antennapod itself, + * can't really enqueue this thing + */ + public FlattrClickWorker(Context context, FlattrThing thing) { + this(context); + extra_flattr_thing = thing; + run_mode = FLATTR_TOAST; + Log.d(TAG, "Going to flattr special thing that is not in the queue: " + thing.getTitle()); + } + + protected void onNoAccessToken() { + Log.w(TAG, "No access token was available"); + } + + protected void onFlattrError() { + FlattrUtils.showErrorDialog(context, errorMsg); + } + + protected void onFlattred() { + String notificationTitle = context.getString(R.string.flattrd_label); + String notificationText = "", notificationSubText = "", notificationBigText = ""; + + // text for successfully flattred items + if (flattrd.size() == 1) + notificationText = String.format(context.getString(R.string.flattr_click_success)); + else if (flattrd.size() > 1) // flattred pending items from queue + notificationText = String.format(context.getString(R.string.flattr_click_success_count, flattrd.size())); + + if (flattrd.size() > 0) { + String acc = ""; + for (String s : flattrd) + acc += s + '\n'; + acc = acc.substring(0, acc.length() - 2); + + notificationBigText = String.format(context.getString(R.string.flattr_click_success_queue), acc); + } + + // add text for failures + if (flattr_failed.size() > 0) { + notificationTitle = context.getString(R.string.flattrd_failed_label); + notificationText = String.format(context.getString(R.string.flattr_click_failure_count), flattr_failed.size()) + + " " + notificationText; + + notificationSubText = flattr_failed.get(0); + + String acc = ""; + for (String s : flattr_failed) + acc += s + '\n'; + acc = acc.substring(0, acc.length() - 2); + + notificationBigText = String.format(context.getString(R.string.flattr_click_failure), acc) + + "\n" + notificationBigText; + } + + Log.d(TAG, "Going to post notification: " + notificationBigText); + + notificationManager.cancel(NOTIFICATION_ID); + + if (run_mode == FLATTR_NOTIFICATION || flattr_failed.size() > 0) { + if (android.os.Build.VERSION.SDK_INT >= 16) { + notificationBuilder = new Notification.BigTextStyle( + new Notification.Builder(context) + .setOngoing(false) + .setContentTitle(notificationTitle) + .setContentText(notificationText) + .setSubText(notificationSubText) + .setSmallIcon(R.drawable.stat_notify_sync)) + .bigText(notificationText + "\n" + notificationBigText); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } else { + notificationCompatBuilder = new NotificationCompat.Builder(context) // need new notificationBuilder and cancel/renotify to get rid of progress bar + .setContentTitle(notificationTitle) + .setContentText(notificationText) + .setSubText(notificationBigText) + .setTicker(notificationTitle) + .setSmallIcon(R.drawable.stat_notify_sync) + .setOngoing(false); + notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build()); + } + } else if (run_mode == FLATTR_TOAST) { + Toast.makeText(context.getApplicationContext(), + notificationText, + Toast.LENGTH_LONG) + .show(); + } + } + + protected void onEnqueue() { + Toast.makeText(context.getApplicationContext(), + R.string.flattr_click_enqueued, + Toast.LENGTH_LONG) + .show(); + } + + protected void onSetupNotification() { + if (android.os.Build.VERSION.SDK_INT >= 16) { + notificationBuilder = new Notification.BigTextStyle( + new Notification.Builder(context) + .setContentTitle(context.getString(R.string.flattring_label)) + .setAutoCancel(true) + .setSmallIcon(R.drawable.stat_notify_sync) + .setProgress(0, 0, true) + .setOngoing(true)); + } else { + notificationCompatBuilder = new NotificationCompat.Builder(context) + .setContentTitle(context.getString(R.string.flattring_label)) + .setAutoCancel(true) + .setSmallIcon(R.drawable.stat_notify_sync) + .setProgress(0, 0, true) + .setOngoing(true); + } + + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + protected void onPostExecute(Void result) { + if (AppConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode); + + switch (exitCode) { + case NO_TOKEN: + notificationManager.cancel(NOTIFICATION_ID); + onNoAccessToken(); + break; + case ENQUEUED: + onEnqueue(); + break; + case EXIT_DEFAULT: + onFlattred(); + break; + case NO_THINGS: // FlattrClickWorker called automatically somewhere to empty flattr queue + notificationManager.cancel(NOTIFICATION_ID); + break; + } + } + + @Override + protected void onPreExecute() { + onSetupNotification(); + } + + private static boolean haveInternetAccess(Context context) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + return (networkInfo != null && networkInfo.isConnectedOrConnecting()); + } + + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) Log.d(TAG, "Starting background work"); + + exitCode = EXIT_DEFAULT; + + if (!FlattrUtils.hasToken()) { + exitCode = NO_TOKEN; + } else if (DBReader.getFlattrQueueEmpty(context) && extra_flattr_thing == null) { + exitCode = NO_THINGS; + } else if (!haveInternetAccess(context) || run_mode == ENQUEUE_ONLY) { + exitCode = ENQUEUED; + } else { + List<FlattrThing> flattrList = DBReader.getFlattrQueue(context); + Log.d(TAG, "flattrQueue processing list with " + flattrList.size() + " items."); + + if (extra_flattr_thing != null) + flattrList.add(extra_flattr_thing); + + flattrd.ensureCapacity(flattrList.size()); + + for (FlattrThing thing : flattrList) { + try { + Log.d(TAG, "flattrQueue processing " + thing.getTitle() + " " + thing.getPaymentLink()); + publishProgress(String.format(context.getString(R.string.flattring_thing), thing.getTitle())); + + thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely + + FlattrUtils.clickUrl(context, thing.getPaymentLink()); + flattrd.add(thing.getTitle()); + + thing.getFlattrStatus().setFlattred(); + } catch (Exception e) { + Log.d(TAG, "flattrQueue processing exception at item " + thing.getTitle() + " " + e.getMessage()); + flattr_failed.ensureCapacity(flattrList.size()); + flattr_failed.add(thing.getTitle() + ": " + e.getMessage()); + } + Log.d(TAG, "flattrQueue processing - going to write thing back to db with flattr_status " + Long.toString(thing.getFlattrStatus().toLong())); + DBWriter.setFlattredStatus(context, thing, false); + } + + } + + return null; + } + + @Override + protected void onProgressUpdate(String... names) { + if (android.os.Build.VERSION.SDK_INT >= 16) { + notificationBuilder.setBigContentTitle(names[0]); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } else { + notificationCompatBuilder.setContentText(names[0]); + notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build()); + } + } + + @SuppressLint("NewApi") + public void executeAsync() { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } else { + execute(); + } + } + + public void executeSync() { + class DirectExecutor implements Executor { + public void execute(Runnable r) { + r.run(); + } + } + FlattrUtils.hasToken(); + executeOnExecutor(new DirectExecutor()); + + } + } diff --git a/src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java b/src/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java new file mode 100644 index 000000000..4974c6b56 --- /dev/null +++ b/src/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.AppConfig; +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 (AppConfig.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 (AppConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status"); + } +} |