diff options
Diffstat (limited to 'src/de/danoeh')
19 files changed, 1118 insertions, 237 deletions
diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 60589bdf5..4aae2b091 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -21,14 +21,18 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.dialog.TimeDialog; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.ShareUtils; import de.danoeh.antennapod.util.StorageUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; import de.danoeh.antennapod.util.playback.MediaPlayerError; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.PlaybackController; +import de.danoeh.antennapod.storage.DBWriter; /** * Provides general features which are both needed for playing audio and video @@ -313,8 +317,16 @@ public abstract class MediaplayerActivity extends ActionBarActivity startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; case R.id.support_item: - new FlattrClickWorker(this, media.getPaymentLink()) - .executeAsync(); + try { + FeedItem feedItem = ((FeedMedia) media).getItem(); + feedItem.getFlattrStatus().setFlattrQueue(); + + DBWriter.setFeedItem(this, feedItem); + new FlattrClickWorker(this).executeAsync(); + } + catch (ClassCastException e) { + Log.d(TAG, "Could not flattr item - most likely external media: " + e.toString()); + } break; case R.id.share_link_item: ShareUtils.shareLink(this, media.getWebsiteLink()); diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index e6fcf5306..4a8dc1882 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -28,7 +28,9 @@ import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.preferences.GpodnetPreferences; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.flattr.FlattrStatus; import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.util.flattr.SimpleFlattrThing; import java.io.File; import java.util.ArrayList; @@ -44,6 +46,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp"; private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; + private static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; private static final String PREF_OPML_EXPORT = "prefOpmlExport"; private static final String PREF_ABOUT = "prefAbout"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; @@ -78,7 +81,11 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { @Override public boolean onPreferenceClick(Preference preference) { new FlattrClickWorker(PreferenceActivity.this, - FlattrUtils.APP_URL).executeAsync(); + new SimpleFlattrThing(PreferenceActivity.this.getString(R.string.app_name), + FlattrUtils.APP_URL, + new FlattrStatus(FlattrStatus.STATUS_QUEUE) + ) + ).executeAsync(); return true; } @@ -297,6 +304,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); + findPreference(PREF_AUTO_FLATTR).setEnabled(hasFlattrToken); findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) .setEnabled(UserPreferences.isEnableAutodownload()); diff --git a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java index 975aa5efe..458b1261b 100644 --- a/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java +++ b/src/de/danoeh/antennapod/asynctask/FlattrClickWorker.java @@ -1,107 +1,310 @@ package de.danoeh.antennapod.asynctask; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.Executor; + import org.shredzone.flattr4j.exception.FlattrException; +import org.shredzone.flattr4j.model.Flattr; import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +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.activity.FeedItemlistActivity; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.util.flattr.FlattrThing; import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; /** Performs a click action in a background thread. */ -public class FlattrClickWorker extends AsyncTask<Void, Void, Void> { +public class FlattrClickWorker extends AsyncTask<Void, String, Void> { protected static final String TAG = "FlattrClickWorker"; protected Context context; - protected String url; + + 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 SUCCESS = 0; + protected final static int EXIT_DEFAULT = 0; protected final static int NO_TOKEN = 1; - protected final static int FLATTR_ERROR = 2; + 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 - public FlattrClickWorker(Context context, String url) { + /** + * @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; - this.url = url; - exitCode = SUCCESS; + exitCode = EXIT_DEFAULT; + + flattrd = new ArrayList<String>(); + flattr_failed = new ArrayList<String>(); + errorMsg = ""; } - protected void onNoAccessToken() { + /* 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"); - if (url.equals(FlattrUtils.APP_URL)) { - FlattrUtils.showNoTokenDialog(context, FlattrUtils.APP_LINK); - } else { - FlattrUtils.showNoTokenDialog(context, url); - } + FlattrUtils.showNoTokenDialog(context, ""); } 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); - protected void onSuccess() { - Toast toast = Toast.makeText(context.getApplicationContext(), - R.string.flattr_click_success, Toast.LENGTH_LONG); - toast.show(); + 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 onSetupProgDialog() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.flattring_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.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); - if (progDialog != null) { - progDialog.dismiss(); - } + switch (exitCode) { case NO_TOKEN: + notificationManager.cancel(NOTIFICATION_ID); onNoAccessToken(); break; - case FLATTR_ERROR: - onFlattrError(); + case ENQUEUED: + onEnqueue(); break; - case SUCCESS: - onSuccess(); + 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() { - onSetupProgDialog(); + 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"); - if (FlattrUtils.hasToken()) { - try { - FlattrUtils.clickUrl(context, url); - } catch (FlattrException e) { - e.printStackTrace(); - exitCode = FLATTR_ERROR; - errorMsg = e.getMessage(); - } - } else { + + 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(); @@ -112,4 +315,16 @@ public class FlattrClickWorker extends AsyncTask<Void, Void, Void> { } } + 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"); + } +} diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java index a99213dc7..994446f43 100644 --- a/src/de/danoeh/antennapod/feed/Feed.java +++ b/src/de/danoeh/antennapod/feed/Feed.java @@ -10,13 +10,15 @@ import java.util.List; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.util.EpisodeFilter; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; /** * Data Object for a whole feed * * @author daniel */ -public class Feed extends FeedFile { +public class Feed extends FeedFile implements FlattrThing { public static final int FEEDFILETYPE_FEED = 0; public static final String TYPE_RSS2 = "rss"; public static final String TYPE_RSS091 = "rss"; @@ -43,6 +45,7 @@ public class Feed extends FeedFile { * Date of last refresh. */ private Date lastUpdate; + private FlattrStatus flattrStatus; private String paymentLink; /** * Feed type, for example RSS 2 or Atom @@ -59,7 +62,7 @@ public class Feed extends FeedFile { */ public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, - String downloadUrl, boolean downloaded) { + String downloadUrl, boolean downloaded, FlattrStatus status) { super(fileUrl, downloadUrl, downloaded); this.id = id; this.title = title; @@ -76,17 +79,29 @@ public class Feed extends FeedFile { this.type = type; this.feedIdentifier = feedIdentifier; this.image = image; + this.flattrStatus = status; items = new ArrayList<FeedItem>(); } /** + * This constructor is used for test purposes and uses a default flattr status object. + */ + public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, + String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, + String downloadUrl, boolean downloaded) { + this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image, + fileUrl, downloadUrl, downloaded, new FlattrStatus()); + } + + /** * This constructor can be used when parsing feed data. Only the 'lastUpdate' and 'items' field are initialized. */ public Feed() { super(); items = new ArrayList<FeedItem>(); lastUpdate = new Date(); + this.flattrStatus = new FlattrStatus(); } /** @@ -96,6 +111,7 @@ public class Feed extends FeedFile { public Feed(String url, Date lastUpdate) { super(null, url, false); this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; + this.flattrStatus = new FlattrStatus(); } /** @@ -105,6 +121,7 @@ public class Feed extends FeedFile { public Feed(String url, Date lastUpdate, String title) { this(url, lastUpdate); this.title = title; + this.flattrStatus = new FlattrStatus(); } /** @@ -238,6 +255,9 @@ public class Feed extends FeedFile { if (other.paymentLink != null) { paymentLink = other.paymentLink; } + if (other.flattrStatus != null) { + flattrStatus = other.flattrStatus; + } } public boolean compareWithOther(Feed other) { @@ -342,6 +362,14 @@ public class Feed extends FeedFile { this.feedIdentifier = feedIdentifier; } + public void setFlattrStatus(FlattrStatus status) { + this.flattrStatus = status; + } + + public FlattrStatus getFlattrStatus() { + return flattrStatus; + } + public String getPaymentLink() { return paymentLink; } diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 9b9375f2e..eaae6ae5e 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -10,6 +10,8 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.util.ShownotesProvider; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; /** * Data Object for a XML message @@ -17,7 +19,7 @@ import de.danoeh.antennapod.util.ShownotesProvider; * @author daniel */ public class FeedItem extends FeedComponent implements - ImageLoader.ImageWorkerTaskResource, ShownotesProvider { + ImageLoader.ImageWorkerTaskResource, ShownotesProvider, FlattrThing { /** * The id/guid that can be found in the rss/atom feed. Might not be set. @@ -42,10 +44,12 @@ public class FeedItem extends FeedComponent implements private boolean read; private String paymentLink; + private FlattrStatus flattrStatus; private List<Chapter> chapters; public FeedItem() { this.read = true; + this.flattrStatus = new FlattrStatus(); } /** @@ -59,6 +63,7 @@ public class FeedItem extends FeedComponent implements this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null; this.read = read; this.feed = feed; + this.flattrStatus = new FlattrStatus(); } public void updateFromOther(FeedItem other) { @@ -195,7 +200,15 @@ public class FeedItem extends FeedComponent implements this.contentEncoded = contentEncoded; } - public String getPaymentLink() { + public void setFlattrStatus(FlattrStatus status) { + this.flattrStatus = status; + } + + public FlattrStatus getFlattrStatus() { + return flattrStatus; + } + + public String getPaymentLink() { return paymentLink; } diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index 492867983..81cae8507 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -10,14 +10,17 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.playback.Playable; public class FeedMedia extends FeedFile implements Playable { + private static final String TAG = "FeedMedia"; public static final int FEEDFILETYPE_FEEDMEDIA = 2; public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; @@ -27,6 +30,7 @@ public class FeedMedia extends FeedFile implements Playable { private int duration; private int position; // Current position in file + private int played_duration; // How many ms of this file have been played (for autoflattring) private long size; // File size in Byte private String mime_type; private volatile FeedItem item; @@ -45,12 +49,13 @@ public class FeedMedia extends FeedFile implements Playable { public FeedMedia(long id, FeedItem item, int duration, int position, long size, String mime_type, String file_url, String download_url, - boolean downloaded, Date playbackCompletionDate) { + boolean downloaded, Date playbackCompletionDate, int played_duration) { super(file_url, download_url, downloaded); this.id = id; this.item = item; this.duration = duration; this.position = position; + this.played_duration = played_duration; this.size = size; this.mime_type = mime_type; this.playbackCompletionDate = playbackCompletionDate == null @@ -137,12 +142,24 @@ public class FeedMedia extends FeedFile implements Playable { this.duration = duration; } - public int getPosition() { + public int getPlayedDuration() { + return played_duration; + } + + public void setPlayedDuration(int played_duration) { + this.played_duration = played_duration; + } + + public int getPosition() { return position; } public void setPosition(int position) { - this.position = position; + final int WAITING_INTERVAL = 5000; + if (position > this.position) + played_duration += Math.min(position - this.position, 1.1*WAITING_INTERVAL); + + this.position = position; } public long getSize() { @@ -215,6 +232,7 @@ public class FeedMedia extends FeedFile implements Playable { dest.writeString(download_url); dest.writeByte((byte) ((downloaded) ? 1 : 0)); dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0); + dest.writeInt(played_duration); } @Override @@ -313,8 +331,8 @@ public class FeedMedia extends FeedFile implements Playable { @Override public void saveCurrentPosition(SharedPreferences pref, int newPosition) { - position = newPosition; - DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); + setPosition(newPosition); + DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); } @Override @@ -358,7 +376,7 @@ public class FeedMedia extends FeedFile implements Playable { final long id = in.readLong(); final long itemID = in.readLong(); FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(), - in.readString(), in.readByte() != 0, new Date(in.readLong())); + in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt()); result.itemID = itemID; return result; } diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index 69714c6a8..2b4b66362 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -40,6 +40,7 @@ public class UserPreferences implements public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; public static final String PREF_AUTO_DELETE = "prefAutoDelete"; + public static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; public static final String PREF_THEME = "prefTheme"; public static final String PREF_DATA_FOLDER = "prefDataFolder"; public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; @@ -50,6 +51,9 @@ public class UserPreferences implements private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; + // TODO: Make this value configurable + private static final double PLAYED_DURATION_AUTOFLATTR_THRESHOLD = 0.8; + private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; private static UserPreferences instance; @@ -63,6 +67,7 @@ public class UserPreferences implements private boolean allowMobileUpdate; private boolean displayOnlyEpisodes; private boolean autoDelete; + private boolean autoFlattr; private int theme; private boolean enableAutodownload; private boolean enableAutodownloadWifiFilter; @@ -112,6 +117,7 @@ public class UserPreferences implements allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); theme = readThemeValue(sp.getString(PREF_THEME, "0")); enableAutodownloadWifiFilter = sp.getBoolean( PREF_ENABLE_AUTODL_WIFI_FILTER, false); @@ -223,6 +229,11 @@ public class UserPreferences implements instanceAvailable(); return instance.autoDelete; } + + public static boolean isAutoFlattr() { + instanceAvailable(); + return instance.autoFlattr; + } public static int getTheme() { instanceAvailable(); @@ -296,6 +307,8 @@ public class UserPreferences implements } else if (key.equals(PREF_AUTO_DELETE)) { autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + } else if (key.equals(PREF_AUTO_FLATTR)) { + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); @@ -508,4 +521,9 @@ public class UserPreferences implements instanceAvailable(); return instance.readEpisodeCacheSizeInternal(valueFromPrefs); } + + public static double getPlayedDurationAutoflattrThreshold() { + instanceAvailable(); + return PLAYED_DURATION_AUTOFLATTR_THRESHOLD; + } } diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackService.java b/src/de/danoeh/antennapod/service/playback/PlaybackService.java index 211d67532..132932c93 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackService.java @@ -23,6 +23,7 @@ import android.util.Pair; import android.view.KeyEvent; import android.view.SurfaceHolder; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; @@ -38,6 +39,7 @@ import de.danoeh.antennapod.storage.DBTasks; import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.util.BitmapDecoder; import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.flattr.FlattrUtils; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.PlaybackController; @@ -344,7 +346,7 @@ public class PlaybackService extends Service { private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() { @Override public void positionSaverTick() { - saveCurrentPosition(); + saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL); } @Override @@ -380,7 +382,7 @@ public class PlaybackService extends Service { case PAUSED: taskManager.cancelPositionSaver(); - saveCurrentPosition(); + saveCurrentPosition(false, 0); taskManager.cancelWidgetUpdater(); stopForeground(true); break; @@ -714,13 +716,32 @@ public class PlaybackService extends Service { /** * Saves the current position of the media file to the DB + * + * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects + * @param deltaPlayedDuration value by which played_duration should be increased. */ - private synchronized void saveCurrentPosition() { + private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { int position = getCurrentPosition(); + int duration = getDuration(); final Playable playable = mediaPlayer.getPSMPInfo().playable; - if (position != INVALID_TIME && playable != null) { + if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) { if (AppConfig.DEBUG) Log.d(TAG, "Saving current position to " + position); + if (updatePlayedDuration && playable instanceof FeedMedia) { + FeedMedia m = (FeedMedia) playable; + FeedItem item = m.getItem(); + m.setPlayedDuration(m.getPlayedDuration() + deltaPlayedDuration); + // Auto flattr + if (FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred() && + (m.getPlayedDuration() > UserPreferences.getPlayedDurationAutoflattrThreshold() * duration)) { + + if (AppConfig.DEBUG) + Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(m.getPlayedDuration()) + + " is " + UserPreferences.getPlayedDurationAutoflattrThreshold() * 100 + "% of file duration " + Integer.toString(duration)); + item.getFlattrStatus().setFlattrQueue(); + DBWriter.setFeedItemFlattrStatus(PodcastApp.getInstance(), item, false); + } + } playable.saveCurrentPosition(PreferenceManager .getDefaultSharedPreferences(getApplicationContext()), position); diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index d950747e6..ccbf6646f 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -1,22 +1,24 @@ package de.danoeh.antennapod.storage; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.service.download.*; +import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + /** * Provides methods for reading data from the AntennaPod database. * In general, all database calls in DBReader-methods are executed on the caller's thread. @@ -124,6 +126,7 @@ public final class DBReader { * Takes a list of FeedItems and loads their corresponding Feed-objects from the database. * The feedID-attribute of a FeedItem must be set to the ID of its feed or the method will * not find the correct feed of an item. + * * @param context A context that is used for opening a database connection. * @param items The FeedItems whose Feed-objects should be loaded. */ @@ -211,7 +214,9 @@ public final class DBReader { .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0)); item.setItemIdentifier(itemlistCursor .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); - + item.setFlattrStatus(new FlattrStatus(itemlistCursor + .getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS))); + // extract chapters boolean hasSimpleChapters = itemlistCursor .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0; @@ -302,7 +307,8 @@ public final class DBReader { cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX), cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX), cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0, - playbackCompletionDate); + playbackCompletionDate, + cursor.getInt(PodDBAdapter.KEY_PLAYED_DURATION_INDEX)); } private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, @@ -330,7 +336,8 @@ public final class DBReader { image, cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FILE_URL), cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOAD_URL), - cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0); + cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0, + new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS))); if (image != null) { image.setFeed(feed); @@ -776,4 +783,48 @@ public final class DBReader { return media; } + + /** + * Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems. + * + * @param context A context that is used for opening a database connection. + * @return The flattr queue as a List. + */ + public static List<FlattrThing> getFlattrQueue(Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List<FlattrThing> result = new ArrayList<FlattrThing>(); + + // load feeds + Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor(); + if (feedCursor.moveToFirst()) { + do { + result.add(extractFeedFromCursorRow(adapter, feedCursor)); + } while (feedCursor.moveToNext()); + } + feedCursor.close(); + + //load feed items + Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor(); + result.addAll(extractItemlistFromCursor(adapter, feedItemCursor)); + feedItemCursor.close(); + + adapter.close(); + Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items."); + return result; + } + + + /** + * Returns true if the flattr queue is empty. + * + * @param context A context that is used for opening a database connection. + */ + public static boolean getFlattrQueueEmpty(Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + boolean empty = adapter.getFlattrQueueSize() == 0; + adapter.close(); + return empty; + } } diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 3b6ab99ce..c6a0dad17 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.util.Log; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; +import de.danoeh.antennapod.asynctask.FlattrStatusFetcher; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.preferences.UserPreferences; @@ -151,6 +153,12 @@ public final class DBTasks { } isRefreshing.set(false); + if (AppConfig.DEBUG) Log.d(TAG, "Flattring all pending things."); + new FlattrClickWorker(context, FlattrClickWorker.FLATTR_NOTIFICATION).executeSync(); // flattr pending things + + if (AppConfig.DEBUG) Log.d(TAG, "Fetching flattr status."); + new FlattrStatusFetcher(context).start(); + GpodnetSyncService.sendSyncIntent(context); autodownloadUndownloadedItems(context); } diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index db498efb5..444e9ea0c 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,15 +1,5 @@ package de.danoeh.antennapod.storage; -import java.io.File; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; - import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -17,12 +7,29 @@ import android.database.Cursor; import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.preferences.GpodnetPreferences; import de.danoeh.antennapod.preferences.PlaybackPreferences; -import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.flattr.FlattrStatus; +import de.danoeh.antennapod.util.flattr.FlattrThing; +import de.danoeh.antennapod.util.flattr.SimpleFlattrThing; +import org.shredzone.flattr4j.model.Flattr; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; /** * Provides methods for writing data to AntennaPod's database. @@ -221,6 +228,9 @@ public class DBWriter { if (AppConfig.DEBUG) Log.d(TAG, "Adding new item to playback history"); media.setPlaybackCompletionDate(new Date()); + // reset played_duration to 0 so that it behaves correctly when the episode is played again + media.setPlayedDuration(0); + PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); adapter.setFeedMediaPlaybackCompletionDate(media); @@ -445,7 +455,7 @@ public class DBWriter { }); } - + /** * Moves the specified item to the top of the queue. * @@ -471,7 +481,7 @@ public class DBWriter { } }); } - + /** * Moves the specified item to the bottom of the queue. * @@ -490,7 +500,7 @@ public class DBWriter { for (long id : queueIdList) { if (id == itemId) { moveQueueItemHelper(context, currentLocation, queueIdList.size() - 1, - broadcastUpdate); + broadcastUpdate); return; } currentLocation++; @@ -499,7 +509,7 @@ public class DBWriter { } }); } - + /** * Changes the position of a FeedItem in the queue. * @@ -523,7 +533,7 @@ public class DBWriter { /** * Changes the position of a FeedItem in the queue. - * + * <p/> * This function must be run using the ExecutorService (dbExec). * * @param context A context that is used for opening a database connection. @@ -534,7 +544,7 @@ public class DBWriter { * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) */ private static void moveQueueItemHelper(final Context context, final int from, - final int to, final boolean broadcastUpdate) { + final int to, final boolean broadcastUpdate) { final PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); final List<FeedItem> queue = DBReader @@ -822,4 +832,125 @@ public class DBWriter { } return false; } + + /** + * Saves the FlattrStatus of a FeedItem object in the database. + * + * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved + */ + public static Future<?> setFeedItemFlattrStatus(final Context context, + final FeedItem item, + final boolean startFlattrClickWorker) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedItemFlattrStatus(item); + adapter.close(); + if (startFlattrClickWorker) { + new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync(); + } + } + }); + } + + /** + * Saves the FlattrStatus of a Feed object in the database. + * + * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved + */ + private static Future<?> setFeedFlattrStatus(final Context context, + final Feed feed, + final boolean startFlattrClickWorker) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedFlattrStatus(feed); + adapter.close(); + if (startFlattrClickWorker) { + new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync(); + } + } + }); + } + + /** + * format an url for querying the database + * (postfix a / and apply percent-encoding) + */ + private static String formatURIForQuery(String uri) { + try { + return URLEncoder.encode(uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri, "UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, e.getMessage()); + return ""; + } + } + + + /** + * Set flattr status of the passed thing (either a FeedItem or a Feed) + * + * @param context + * @param thing + * @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved + * @return + */ + public static Future<?> setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) { + // must propagate this to back db + if (thing instanceof FeedItem) + return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker); + else if (thing instanceof Feed) + return setFeedFlattrStatus(context, (Feed) thing, startFlattrClickWorker); + else if (thing instanceof SimpleFlattrThing) { + } // SimpleFlattrThings are generated on the fly and do not have DB backing + else + Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed nor SimpleFlattrThing"); + + return null; + } + + /** + * Reset flattr status to unflattrd for all items + */ + public static Future<?> clearAllFlattrStatus(final Context context) { + Log.d(TAG, "clearAllFlattrStatus()"); + return dbExec.submit(new Runnable() { + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.clearAllFlattrStatus(); + adapter.close(); + } + }); + } + + /** + * Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp, + * where the information has been retrieved from the flattr API + */ + public static Future<?> setFlattredStatus(final Context context, final List<Flattr> flattrList) { + Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items"); + // clear flattr status in db + clearAllFlattrStatus(context); + + // submit list with flattred things having normalized URLs to db + return dbExec.submit(new Runnable() { + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + for (Flattr flattr : flattrList) { + adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime())); + } + adapter.close(); + } + }); + } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 049b78dc0..738ccbf50 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -1,8 +1,5 @@ package de.danoeh.antennapod.storage; -import java.util.Arrays; -import java.util.List; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -16,6 +13,10 @@ import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.util.flattr.FlattrStatus; + +import java.util.Arrays; +import java.util.List; // TODO Remove media column from feeditem table @@ -24,7 +25,7 @@ import de.danoeh.antennapod.service.download.DownloadStatus; */ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 11; public static final String DATABASE_NAME = "Antennapod.db"; /** @@ -54,6 +55,7 @@ public class PodDBAdapter { public static final int KEY_IMAGE_INDEX = 11; public static final int KEY_TYPE_INDEX = 12; public static final int KEY_FEED_IDENTIFIER_INDEX = 13; + public static final int KEY_FEED_FLATTR_STATUS_INDEX = 14; // ----------- FeedItem indices public static final int KEY_CONTENT_ENCODED_INDEX = 2; public static final int KEY_PUBDATE_INDEX = 3; @@ -62,6 +64,7 @@ public class PodDBAdapter { public static final int KEY_FEED_INDEX = 9; public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10; public static final int KEY_ITEM_IDENTIFIER_INDEX = 11; + public static final int KEY_ITEM_FLATTR_STATUS_INDEX = 12; // ---------- FeedMedia indices public static final int KEY_DURATION_INDEX = 1; public static final int KEY_POSITION_INDEX = 5; @@ -69,6 +72,7 @@ public class PodDBAdapter { public static final int KEY_MIME_TYPE_INDEX = 7; public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8; public static final int KEY_MEDIA_FEEDITEM_INDEX = 9; + public static final int KEY_PLAYED_DURATION_INDEX = 10; // --------- Download log indices public static final int KEY_FEEDFILE_INDEX = 1; public static final int KEY_FEEDFILETYPE_INDEX = 2; @@ -119,12 +123,14 @@ public class PodDBAdapter { public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; public static final String KEY_TYPE = "type"; public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; + public static final String KEY_FLATTR_STATUS = "flattr_status"; public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; public static final String KEY_REASON_DETAILED = "reason_detailed"; public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; public static final String KEY_CHAPTER_TYPE = "type"; public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; public static final String KEY_AUTO_DOWNLOAD = "auto_download"; + public static final String KEY_PLAYED_DURATION = "played_duration"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -146,7 +152,8 @@ public class PodDBAdapter { + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1)"; + + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1," + + KEY_FLATTR_STATUS + " INTEGER)"; private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -154,7 +161,8 @@ public class PodDBAdapter { + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," - + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)"; + + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + + KEY_FLATTR_STATUS + " INTEGER)"; private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -167,7 +175,8 @@ public class PodDBAdapter { + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," - + KEY_FEEDITEM + " INTEGER)"; + + KEY_FEEDITEM + " INTEGER," + + KEY_PLAYED_DURATION + " INTEGER)"; private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE @@ -208,6 +217,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_TYPE, TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER, TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD, + TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS }; // column indices for FEED_SEL_STD @@ -226,6 +236,7 @@ public class PodDBAdapter { public static final int IDX_FEED_SEL_STD_TYPE = 12; public static final int IDX_FEED_SEL_STD_FEED_IDENTIFIER = 13; public static final int IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD = 14; + public static final int IDX_FEED_SEL_STD_FLATTR_STATUS = 15; /** @@ -241,7 +252,8 @@ public class PodDBAdapter { TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA, TABLE_NAME_FEED_ITEMS + "." + KEY_FEED, TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS, - TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER}; + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER, + TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS}; /** * Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries. @@ -265,6 +277,7 @@ public class PodDBAdapter { public static final int IDX_FI_SMALL_FEED = 7; public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8; public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9; + public static final int IDX_FI_SMALL_FLATTR_STATUS = 10; /** * Select id, description and content-encoded column from feeditems. @@ -346,6 +359,10 @@ public class PodDBAdapter { values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime()); values.put(KEY_TYPE, feed.getType()); values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); + + Log.d(TAG, "Setting feed with flattr status " + feed.getTitle() + ": " + feed.getFlattrStatus().toLong()); + + values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); if (feed.getId() == 0) { // Create new entry if (AppConfig.DEBUG) @@ -435,6 +452,7 @@ public class PodDBAdapter { ContentValues values = new ContentValues(); values.put(KEY_POSITION, media.getPosition()); values.put(KEY_DURATION, media.getDuration()); + values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(media.getId())}); } else { @@ -446,6 +464,7 @@ public class PodDBAdapter { if (media.getId() != 0) { ContentValues values = new ContentValues(); values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime()); + values.put(KEY_PLAYED_DURATION, media.getPlayedDuration()); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(media.getId())}); } else { @@ -470,6 +489,56 @@ public class PodDBAdapter { } /** + * Update the flattr status of a feed + */ + public void setFeedFlattrStatus(Feed feed) { + ContentValues values = new ContentValues(); + values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); + } + + /** + * Get all feeds in the flattr queue. + */ + public Cursor getFeedsInFlattrQueueCursor() { + return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_FLATTR_STATUS + "=?", + new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)},null, null, null); + } + + /** + * Get all feed items in the flattr queue. + */ + public Cursor getFeedItemsInFlattrQueueCursor() { + return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FLATTR_STATUS + "=?", + new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)},null, null, null); + } + + /** + * Counts feeds and feed items in the flattr queue + */ + public int getFlattrQueueSize() { + int res = 0; + Cursor c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", + TABLE_NAME_FEEDS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); + if (c.moveToFirst()) { + res = c.getInt(0); + c.close(); + } else { + Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feeds"); + } + c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s", + TABLE_NAME_FEED_ITEMS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null); + if (c.moveToFirst()) { + res += c.getInt(0); + c.close(); + } else { + Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feed items"); + } + + return res; + } + + /** * Updates the download URL of a Feed. */ public void setFeedDownloadUrl(String original, String updated) { @@ -496,6 +565,63 @@ public class PodDBAdapter { } /** + * Update the flattr status of a FeedItem + */ + public void setFeedItemFlattrStatus(FeedItem feedItem) { + ContentValues values = new ContentValues(); + values.put(KEY_FLATTR_STATUS, feedItem.getFlattrStatus().toLong()); + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(feedItem.getId())}); + } + + /** + * Update the flattr status of a feed or feed item specified by its payment link + * and the new flattr status to use + */ + public void setItemFlattrStatus(String url, FlattrStatus status) + { + //Log.d(TAG, "setItemFlattrStatus(" + url + ") = " + status.toString()); + ContentValues values = new ContentValues(); + values.put(KEY_FLATTR_STATUS, status.toLong()); + + // regexps in sqlite would be neat! + String[] query_urls = new String[]{ + "*" + url + "&*", + "*" + url + "%2F&*", + "*" + url + "", + "*" + url + "%2F" + }; + + if (db.update(TABLE_NAME_FEEDS, values, + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls) > 0) + { + Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in Feeds table"); + return; + } + if (db.update(TABLE_NAME_FEED_ITEMS, values, + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?" + + " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls) > 0) + { + Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in FeedsItems table"); + } + } + + /** + * Reset flattr status to unflattrd for all items + */ + public void clearAllFlattrStatus() + { + ContentValues values = new ContentValues(); + values.put(KEY_FLATTR_STATUS, 0); + db.update(TABLE_NAME_FEEDS, values, null, null); + db.update(TABLE_NAME_FEED_ITEMS, values, null, null); + } + + /** * Inserts or updates a feeditem entry * * @param item The FeedItem @@ -522,6 +648,7 @@ public class PodDBAdapter { values.put(KEY_READ, item.isRead()); values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); + values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong()); if (item.getId() == 0) { item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); } else { @@ -1170,6 +1297,17 @@ public class PodDBAdapter { + " ADD COLUMN " + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1"); } + if (oldVersion <= 10) { + db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + + " ADD COLUMN " + KEY_FLATTR_STATUS + + " INTEGER"); + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + KEY_FLATTR_STATUS + + " INTEGER"); + db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA + + " ADD COLUMN " + KEY_PLAYED_DURATION + + " INTEGER"); + } } } } diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java b/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java new file mode 100644 index 000000000..a1d6d3bc4 --- /dev/null +++ b/src/de/danoeh/antennapod/util/flattr/FlattrStatus.java @@ -0,0 +1,68 @@ +package de.danoeh.antennapod.util.flattr; + +import java.util.Calendar; + +public class FlattrStatus { + public static final int STATUS_UNFLATTERED = 0; + public static final int STATUS_QUEUE = 1; + public static final int STATUS_FLATTRED = 2; + + private int status = STATUS_UNFLATTERED; + private Calendar lastFlattred; + + public FlattrStatus() { + status = STATUS_UNFLATTERED; + lastFlattred = Calendar.getInstance(); + } + + public FlattrStatus(long status) { + lastFlattred = Calendar.getInstance(); + fromLong(status); + } + + public void setFlattred() { + status = STATUS_FLATTRED; + lastFlattred = Calendar.getInstance(); + } + + public void setUnflattred() { + status = STATUS_UNFLATTERED; + } + + public boolean getUnflattred() { + return status == STATUS_UNFLATTERED; + } + + public void setFlattrQueue() { + if (flattrable()) + status = STATUS_QUEUE; + } + + public void fromLong(long status) { + if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) + this.status = (int) status; + else { + this.status = STATUS_FLATTRED; + lastFlattred.setTimeInMillis(status); + } + } + + public long toLong() { + if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE) + return status; + else { + return lastFlattred.getTimeInMillis(); + } + } + + public boolean flattrable() { + Calendar firstOfMonth = Calendar.getInstance(); + firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); + + return (status == STATUS_UNFLATTERED) || (status == STATUS_FLATTRED && firstOfMonth.after(lastFlattred) ); + } + + public boolean getFlattrQueue() { + return status == STATUS_QUEUE; + } +} diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrThing.java b/src/de/danoeh/antennapod/util/flattr/FlattrThing.java new file mode 100644 index 000000000..872132517 --- /dev/null +++ b/src/de/danoeh/antennapod/util/flattr/FlattrThing.java @@ -0,0 +1,9 @@ +package de.danoeh.antennapod.util.flattr; + +import de.danoeh.antennapod.util.flattr.FlattrStatus; + +public interface FlattrThing { + public String getTitle(); + public String getPaymentLink(); + public FlattrStatus getFlattrStatus(); +} diff --git a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java index ca2c9eb0f..215e67e55 100644 --- a/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java +++ b/src/de/danoeh/antennapod/util/flattr/FlattrUtils.java @@ -1,9 +1,16 @@ package de.danoeh.antennapod.util.flattr; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.EnumSet; +import java.util.List; +import java.util.ListIterator; +import java.util.TimeZone; import org.shredzone.flattr4j.FlattrService; import org.shredzone.flattr4j.exception.FlattrException; +import org.shredzone.flattr4j.model.Flattr; import org.shredzone.flattr4j.model.Thing; import org.shredzone.flattr4j.oauth.AccessToken; import org.shredzone.flattr4j.oauth.AndroidAuthenticator; @@ -23,6 +30,7 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.FlattrAuthActivity; import de.danoeh.antennapod.asynctask.FlattrTokenFetcher; +import de.danoeh.antennapod.storage.DBWriter; /** Utility methods for doing something with flattr. */ @@ -119,6 +127,58 @@ public class FlattrUtils { Log.e(TAG, "clickUrl was called with null access token"); } } + + public static List<Flattr> retrieveFlattredThings() + throws FlattrException { + ArrayList<Flattr> myFlattrs = new ArrayList<Flattr>(); + + if (hasToken()) { + FlattrService fs = FlattrServiceCreator.getService(retrieveToken()); + + Calendar firstOfMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + firstOfMonth.set(Calendar.MILLISECOND, 0); + firstOfMonth.set(Calendar.SECOND, 0); + firstOfMonth.set(Calendar.MINUTE, 0); + firstOfMonth.set(Calendar.HOUR_OF_DAY, 0); + firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH)); + + Date firstOfMonthDate = firstOfMonth.getTime(); + + // subscriptions some times get flattrd slightly before midnight - give it an hour leeway + firstOfMonthDate = new Date(firstOfMonthDate.getTime() - 60*60*1000); + + final int FLATTR_COUNT = 30; + final int FLATTR_MAXPAGE = 5; + + int page = 0; + do { + myFlattrs.ensureCapacity(FLATTR_COUNT*(page+1)); + + for (Flattr fl: fs.getMyFlattrs(FLATTR_COUNT, page)) { + if (fl.getCreated().after(firstOfMonthDate)) + myFlattrs.add(fl); + else + break; + } + page++; + } + while (myFlattrs.get(myFlattrs.size()-1).getCreated().after( firstOfMonthDate ) && page < FLATTR_MAXPAGE); + + if (AppConfig.DEBUG) { + Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate); + + for (Flattr fl: myFlattrs) { + Thing thing = fl.getThing(); + Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated()); + } + } + + } else { + Log.e(TAG, "retrieveFlattrdThings was called with null access token"); + } + + return myFlattrs; + } public static void handleCallback(Context context, Uri uri) { AndroidAuthenticator auth = createAuthenticator(); @@ -131,7 +191,8 @@ public class FlattrUtils { deleteToken(); FlattrServiceCreator.deleteFlattrService(); showRevokeDialog(context); - } + DBWriter.clearAllFlattrStatus(context); + } // ------------------------------------------------ DIALOGS diff --git a/src/de/danoeh/antennapod/util/flattr/SimpleFlattrThing.java b/src/de/danoeh/antennapod/util/flattr/SimpleFlattrThing.java new file mode 100644 index 000000000..296610871 --- /dev/null +++ b/src/de/danoeh/antennapod/util/flattr/SimpleFlattrThing.java @@ -0,0 +1,30 @@ +package de.danoeh.antennapod.util.flattr; + +/* SimpleFlattrThing is a trivial implementation of the FlattrThing interface */ +public class SimpleFlattrThing implements FlattrThing { + public SimpleFlattrThing(String title, String url, FlattrStatus status) + { + this.title = title; + this.url = url; + this.status = status; + } + + public String getTitle() + { + return this.title; + } + + public String getPaymentLink() + { + return this.url; + } + + public FlattrStatus getFlattrStatus() + { + return this.status; + } + + private String title; + private String url; + private FlattrStatus status; +} diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index 38d990b4b..510f3bae8 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.net.Uri; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.storage.DBTasks; @@ -15,159 +14,159 @@ import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.ShareUtils; -/** Handles interactions with the FeedItemMenu. */ +/** + * Handles interactions with the FeedItemMenu. + */ public class FeedItemMenuHandler { - private FeedItemMenuHandler() { + private static final String TAG = "FeedItemMenuHandler"; - } + private FeedItemMenuHandler() { - /** - * Used by the MenuHandler to access different types of menus through one - * interface - */ - public interface MenuInterface { - /** - * Implementations of this method should call findItem(id) on their - * menu-object and call setVisibility(visibility) on the returned - * MenuItem object. - */ - abstract void setItemVisibility(int id, boolean visible); - } + } - /** - * This method should be called in the prepare-methods of menus. It changes - * the visibility of the menu items depending on a FeedItem's attributes. - * - * @param mi - * An instance of MenuInterface that the method uses to change a - * MenuItem's visibility - * @param selectedItem - * The FeedItem for which the menu is supposed to be prepared - * @param showExtendedMenu - * True if MenuItems that let the user share information about - * the FeedItem and visit its website should be set visible. This - * parameter should be set to false if the menu space is limited. - * @param queueAccess - * Used for testing if the queue contains the selected item - * @return Returns true if selectedItem is not null. - * */ - public static boolean onPrepareMenu(MenuInterface mi, - FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { + /** + * Used by the MenuHandler to access different types of menus through one + * interface + */ + public interface MenuInterface { + /** + * Implementations of this method should call findItem(id) on their + * menu-object and call setVisibility(visibility) on the returned + * MenuItem object. + */ + abstract void setItemVisibility(int id, boolean visible); + } + + /** + * This method should be called in the prepare-methods of menus. It changes + * the visibility of the menu items depending on a FeedItem's attributes. + * + * @param mi An instance of MenuInterface that the method uses to change a + * MenuItem's visibility + * @param selectedItem The FeedItem for which the menu is supposed to be prepared + * @param showExtendedMenu True if MenuItems that let the user share information about + * the FeedItem and visit its website should be set visible. This + * parameter should be set to false if the menu space is limited. + * @param queueAccess Used for testing if the queue contains the selected item + * @return Returns true if selectedItem is not null. + */ + public static boolean onPrepareMenu(MenuInterface mi, + FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { if (selectedItem == null) { return false; } - DownloadRequester requester = DownloadRequester.getInstance(); - boolean hasMedia = selectedItem.getMedia() != null; - boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); - boolean downloading = hasMedia - && requester.isDownloadingFile(selectedItem.getMedia()); - boolean notLoadedAndNotLoading = hasMedia && (!downloaded) - && (!downloading); - boolean isPlaying = hasMedia - && selectedItem.getState() == FeedItem.State.PLAYING; + DownloadRequester requester = DownloadRequester.getInstance(); + boolean hasMedia = selectedItem.getMedia() != null; + boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); + boolean downloading = hasMedia + && requester.isDownloadingFile(selectedItem.getMedia()); + boolean notLoadedAndNotLoading = hasMedia && (!downloaded) + && (!downloading); + boolean isPlaying = hasMedia + && selectedItem.getState() == FeedItem.State.PLAYING; - FeedItem.State state = selectedItem.getState(); + FeedItem.State state = selectedItem.getState(); - if (!isPlaying) { - mi.setItemVisibility(R.id.skip_episode_item, false); - } - if (!downloaded || isPlaying) { - mi.setItemVisibility(R.id.play_item, false); - mi.setItemVisibility(R.id.remove_item, false); - } - if (!notLoadedAndNotLoading) { - mi.setItemVisibility(R.id.download_item, false); - } - if (!(notLoadedAndNotLoading | downloading) | isPlaying) { - mi.setItemVisibility(R.id.stream_item, false); - } - if (!downloading) { - mi.setItemVisibility(R.id.cancel_download_item, false); - } + if (!isPlaying) { + mi.setItemVisibility(R.id.skip_episode_item, false); + } + if (!downloaded || isPlaying) { + mi.setItemVisibility(R.id.play_item, false); + mi.setItemVisibility(R.id.remove_item, false); + } + if (!notLoadedAndNotLoading) { + mi.setItemVisibility(R.id.download_item, false); + } + if (!(notLoadedAndNotLoading | downloading) | isPlaying) { + mi.setItemVisibility(R.id.stream_item, false); + } + if (!downloading) { + mi.setItemVisibility(R.id.cancel_download_item, false); + } - boolean isInQueue = queueAccess.contains(selectedItem.getId()); - if (!isInQueue || isPlaying) { - mi.setItemVisibility(R.id.remove_from_queue_item, false); - } - if (!(!isInQueue && selectedItem.getMedia() != null)) { - mi.setItemVisibility(R.id.add_to_queue_item, false); - } - if (!showExtendedMenu || selectedItem.getLink() == null) { - mi.setItemVisibility(R.id.share_link_item, false); - } + boolean isInQueue = queueAccess.contains(selectedItem.getId()); + if (!isInQueue || isPlaying) { + mi.setItemVisibility(R.id.remove_from_queue_item, false); + } + if (!(!isInQueue && selectedItem.getMedia() != null)) { + mi.setItemVisibility(R.id.add_to_queue_item, false); + } + if (!showExtendedMenu || selectedItem.getLink() == null) { + mi.setItemVisibility(R.id.share_link_item, false); + } - if (!AppConfig.DEBUG - || !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) { - mi.setItemVisibility(R.id.mark_unread_item, false); - } - if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) { - mi.setItemVisibility(R.id.mark_read_item, false); - } + if (!AppConfig.DEBUG + || !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) { + mi.setItemVisibility(R.id.mark_unread_item, false); + } + if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) { + mi.setItemVisibility(R.id.mark_read_item, false); + } - if (!showExtendedMenu || selectedItem.getLink() == null) { - mi.setItemVisibility(R.id.visit_website_item, false); - } + if (!showExtendedMenu || selectedItem.getLink() == null) { + mi.setItemVisibility(R.id.visit_website_item, false); + } - if (selectedItem.getPaymentLink() == null) { - mi.setItemVisibility(R.id.support_item, false); - } - return true; - } + if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { + mi.setItemVisibility(R.id.support_item, false); + } + return true; + } - public static boolean onMenuItemClicked(Context context, int menuItemId, - FeedItem selectedItem) throws DownloadRequestException { - DownloadRequester requester = DownloadRequester.getInstance(); - switch (menuItemId) { - case R.id.skip_episode_item: - context.sendBroadcast(new Intent( - PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); - break; - case R.id.download_item: - DBTasks.downloadFeedItems(context, selectedItem); - break; - case R.id.play_item: - DBTasks.playMedia(context, selectedItem.getMedia(), true, true, - false); - break; - case R.id.remove_item: - DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); - break; - case R.id.cancel_download_item: - requester.cancelDownload(context, selectedItem.getMedia()); - break; - case R.id.mark_read_item: - DBWriter.markItemRead(context, selectedItem, true, true); - break; - case R.id.mark_unread_item: - DBWriter.markItemRead(context, selectedItem, false, true); - break; - case R.id.add_to_queue_item: - DBWriter.addQueueItem(context, selectedItem.getId()); - break; - case R.id.remove_from_queue_item: - DBWriter.removeQueueItem(context, selectedItem.getId(), true); - break; - case R.id.stream_item: - DBTasks.playMedia(context, selectedItem.getMedia(), true, true, - true); - break; - case R.id.visit_website_item: - Uri uri = Uri.parse(selectedItem.getLink()); - context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); - break; - case R.id.support_item: - new FlattrClickWorker(context, selectedItem.getPaymentLink()) - .executeAsync(); - break; - case R.id.share_link_item: - ShareUtils.shareFeedItemLink(context, selectedItem); - break; - default: - return false; - } - // Refresh menu state + public static boolean onMenuItemClicked(Context context, int menuItemId, + FeedItem selectedItem) throws DownloadRequestException { + DownloadRequester requester = DownloadRequester.getInstance(); + switch (menuItemId) { + case R.id.skip_episode_item: + context.sendBroadcast(new Intent( + PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); + break; + case R.id.download_item: + DBTasks.downloadFeedItems(context, selectedItem); + break; + case R.id.play_item: + DBTasks.playMedia(context, selectedItem.getMedia(), true, true, + false); + break; + case R.id.remove_item: + DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId()); + break; + case R.id.cancel_download_item: + requester.cancelDownload(context, selectedItem.getMedia()); + break; + case R.id.mark_read_item: + DBWriter.markItemRead(context, selectedItem, true, true); + break; + case R.id.mark_unread_item: + DBWriter.markItemRead(context, selectedItem, false, true); + break; + case R.id.add_to_queue_item: + DBWriter.addQueueItem(context, selectedItem.getId()); + break; + case R.id.remove_from_queue_item: + DBWriter.removeQueueItem(context, selectedItem.getId(), true); + break; + case R.id.stream_item: + DBTasks.playMedia(context, selectedItem.getMedia(), true, true, + true); + break; + case R.id.visit_website_item: + Uri uri = Uri.parse(selectedItem.getLink()); + context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + break; + case R.id.support_item: + selectedItem.getFlattrStatus().setFlattrQueue(); + DBWriter.setFlattredStatus(context, selectedItem, true); + break; + case R.id.share_link_item: + ShareUtils.shareFeedItemLink(context, selectedItem); + break; + default: + return false; + } + // Refresh menu state - return true; - } + return true; + } } diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index 27b1a8a8c..86808b609 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -8,6 +8,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.FeedInfoActivity; @@ -19,6 +23,7 @@ import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ShareUtils; +import de.danoeh.antennapod.util.flattr.FlattrStatus; /** Handles interactions with the FeedItemMenu. */ public class FeedMenuHandler { @@ -38,9 +43,10 @@ public class FeedMenuHandler { Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.mark_all_read_item).setVisible( selectedFeed.hasNewItems(true)); - if (selectedFeed.getPaymentLink() != null) { + if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) menu.findItem(R.id.support_item).setVisible(true); - } + else + menu.findItem(R.id.support_item).setVisible(false); MenuItem refresh = menu.findItem(R.id.refresh_item); if (DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFile( @@ -78,8 +84,8 @@ public class FeedMenuHandler { context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; case R.id.support_item: - new FlattrClickWorker(context, selectedFeed.getPaymentLink()) - .executeAsync(); + selectedFeed.getFlattrStatus().setFlattrQueue(); + DBWriter.setFlattredStatus(context, selectedFeed, true); break; case R.id.share_link_item: ShareUtils.shareFeedlink(context, selectedFeed); |