summaryrefslogtreecommitdiff
path: root/core/src/main/java/de/danoeh
diff options
context:
space:
mode:
authorTom Hennen <TomHennen@users.noreply.github.com>2015-06-16 20:17:04 -0400
committerTom Hennen <TomHennen@users.noreply.github.com>2015-06-16 20:17:04 -0400
commitec4ed747b6de9ab98d3ad31a0256a77b8e8cc25c (patch)
tree7dfa03fff2e7a7a9fc0d32b74e313668e9b6bcb2 /core/src/main/java/de/danoeh
parentf88c80cced6c3b529a6ef1addafaa930fb41d58f (diff)
parent3d102449bc2bf78b6c5645ee738e6658c9cb155f (diff)
downloadAntennaPod-ec4ed747b6de9ab98d3ad31a0256a77b8e8cc25c.zip
Merge pull request #881 from AntennaPod/develop1.2
Release of 1.2
Diffstat (limited to 'core/src/main/java/de/danoeh')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java110
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java40
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java82
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java75
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java51
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java109
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java315
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java53
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java88
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java577
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java25
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java288
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java47
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java98
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java198
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java103
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java36
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java102
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java345
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java66
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java219
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java201
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java194
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java114
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntList.java240
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java252
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java240
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java56
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java86
42 files changed, 3408 insertions, 1254 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
index b6ece6dc8..4f2d5b204 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
@@ -6,8 +6,12 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.Log;
+import com.squareup.okhttp.Interceptor;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Response;
import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.OkHttpDownloader;
@@ -22,13 +26,18 @@ import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+
/**
* Provides access to Picasso instances.
*/
public class PicassoProvider {
+
private static final String TAG = "PicassoProvider";
private static final boolean DEBUG = false;
@@ -56,10 +65,12 @@ public class PicassoProvider {
if (picassoSetup) {
return;
}
+ OkHttpClient client = new OkHttpClient();
+ client.interceptors().add(new BasicAuthenticationInterceptor(appContext));
Picasso picasso = new Picasso.Builder(appContext)
.indicatorsEnabled(DEBUG)
.loggingEnabled(DEBUG)
- .downloader(new OkHttpDownloader(appContext))
+ .downloader(new OkHttpDownloader(client))
.addRequestHandler(new MediaRequestHandler(appContext))
.executor(getExecutorService())
.memoryCache(getMemoryCache(appContext))
@@ -75,6 +86,48 @@ public class PicassoProvider {
picassoSetup = true;
}
+ private static class BasicAuthenticationInterceptor implements Interceptor {
+
+ private final Context context;
+
+ public BasicAuthenticationInterceptor(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ com.squareup.okhttp.Request request = chain.request();
+ String url = request.urlString();
+ String authentication = DBReader.getImageAuthentication(context, url);
+
+ if(TextUtils.isEmpty(authentication)) {
+ Log.d(TAG, "no credentials for '" + url + "'");
+ return chain.proceed(request);
+ }
+
+ // add authentication
+ String[] auth = authentication.split(":");
+ String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1");
+ com.squareup.okhttp.Request newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with ISO-8859-1 encoding");
+ Response response = chain.proceed(newRequest);
+ if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8");
+ newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with UTF-8 encoding");
+ return chain.proceed(newRequest);
+ } else {
+ return response;
+ }
+ }
+ }
+
private static class MediaRequestHandler extends RequestHandler {
final Context context;
@@ -90,7 +143,7 @@ public class PicassoProvider {
}
@Override
- public Result load(Request data) throws IOException {
+ public Result load(Request data, int networkPolicy) throws IOException {
Bitmap bitmap = null;
MediaMetadataRetriever mmr = null;
try {
@@ -109,13 +162,7 @@ public class PicassoProvider {
}
if (bitmap == null) {
- // check for fallback Uri
- String fallbackParam = data.uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK);
-
- if (fallbackParam != null) {
- Uri fallback = Uri.parse(fallbackParam);
- bitmap = decodeStreamFromFile(data, fallback);
- }
+ Log.wtf(TAG, "THIS SHOULD NEVER EVER HAPPEN!!");
}
return new Result(bitmap, Picasso.LoadedFrom.DISK);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
index 5a2cfa40e..20a85d43f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
@@ -5,8 +5,6 @@ import android.util.Log;
import org.apache.commons.lang3.Validate;
-import de.danoeh.antennapod.core.BuildConfig;
-
import java.util.AbstractQueue;
import java.util.Observable;
import java.util.Observer;
@@ -26,7 +24,6 @@ public class EventDistributor extends Observable {
public static final int FEED_LIST_UPDATE = 1;
public static final int UNREAD_ITEMS_UPDATE = 2;
- public static final int QUEUE_UPDATE = 4;
public static final int DOWNLOADLOG_UPDATE = 8;
public static final int PLAYBACK_HISTORY_UPDATE = 16;
public static final int DOWNLOAD_QUEUED = 32;
@@ -71,23 +68,17 @@ public class EventDistributor extends Observable {
private void processEventQueue() {
Integer result = 0;
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Processing event queue. Number of events: "
- + events.size());
+ Log.d(TAG, "Processing event queue. Number of events: " + events.size());
for (Integer current = events.poll(); current != null; current = events
.poll()) {
result |= current;
}
if (result != 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Notifying observers. Data: " + result);
+ Log.d(TAG, "Notifying observers. Data: " + result);
setChanged();
notifyObservers(result);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Event queue didn't contain any new events. Observers will not be notified.");
+ Log.d(TAG, "Event queue didn't contain any new events. Observers will not be notified.");
}
}
@@ -105,10 +96,6 @@ public class EventDistributor extends Observable {
addEvent(UNREAD_ITEMS_UPDATE);
}
- public void sendQueueUpdateBroadcast() {
- addEvent(QUEUE_UPDATE);
- }
-
public void sendFeedUpdateBroadcast() {
addEvent(FEED_LIST_UPDATE);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index 8860653a1..29ba721fe 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed;
import android.content.Context;
import android.net.Uri;
+import android.support.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
@@ -10,9 +11,7 @@ import java.util.Date;
import java.util.List;
import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.util.EpisodeFilter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
@@ -81,12 +80,20 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
private String nextPageLink;
+ private boolean lastUpdateFailed;
+
+ /**
+ * Contains property strings. If such a property applies to a feed item, it is not shown in the feed list
+ */
+ private FeedItemFilter itemfilter;
+
/**
* This constructor is used for restoring a feed from the database.
*/
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, FlattrStatus status, boolean paged, String nextPageLink) {
+ String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
+ String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.title = title;
@@ -106,8 +113,13 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
-
- items = new ArrayList<FeedItem>();
+ this.items = new ArrayList<FeedItem>();
+ if(filter != null) {
+ this.itemfilter = new FeedItemFilter(filter);
+ } else {
+ this.itemfilter = new FeedItemFilter(new String[0]);
+ }
+ this.lastUpdateFailed = lastUpdateFailed;
}
/**
@@ -117,7 +129,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
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(), false, null);
+ fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
}
/**
@@ -125,7 +137,6 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
public Feed() {
super();
- items = new ArrayList<FeedItem>();
lastUpdate = new Date();
this.flattrStatus = new FlattrStatus();
}
@@ -159,53 +170,15 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
preferences = new FeedPreferences(0, true, username, password);
}
- /**
- * Returns the number of FeedItems where 'read' is false. If the 'display
- * only episodes' - preference is set to true, this method will only count
- * items with episodes.
- */
- public int getNumOfNewItems() {
- int count = 0;
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!UserPreferences.isDisplayOnlyEpisodes()
- || item.getMedia() != null) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Returns the number of FeedItems where the media started to play but
- * wasn't finished yet.
- */
- public int getNumOfStartedItems() {
- int count = 0;
-
- for (FeedItem item : items) {
- FeedItem.State state = item.getState();
- if (state == FeedItem.State.IN_PROGRESS
- || state == FeedItem.State.PLAYING) {
- count++;
- }
- }
- return count;
- }
/**
* Returns true if at least one item in the itemlist is unread.
*
- * @param enableEpisodeFilter true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
*/
- public boolean hasNewItems(boolean enableEpisodeFilter) {
+ public boolean hasNewItems() {
for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!(enableEpisodeFilter && UserPreferences
- .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
+ if (item.getState() == FeedItem.State.UNREAD) {
+ if (item.getMedia() != null) {
return true;
}
}
@@ -216,30 +189,17 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
/**
* Returns the number of FeedItems.
*
- * @param enableEpisodeFilter true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
*/
- public int getNumOfItems(boolean enableEpisodeFilter) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.countItemsWithEpisodes(items);
- } else {
- return items.size();
- }
+ public int getNumOfItems() {
+ return items.size();
}
/**
* Returns the item at the specified index.
*
- * @param enableEpisodeFilter true if this method should ignore items without episdodes if
- * the episodes filter has been enabled by the user.
*/
- public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.accessEpisodeByIndex(items, position);
- } else {
- return items.get(position);
- }
+ public FeedItem getItemAtIndex(int position) {
+ return items.get(position);
}
/**
@@ -516,4 +476,24 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
public void setNextPageLink(String nextPageLink) {
this.nextPageLink = nextPageLink;
}
+
+ @Nullable
+ public FeedItemFilter getItemFilter() {
+ return itemfilter;
+ }
+
+ public void setHiddenItemProperties(String[] properties) {
+ if (properties != null) {
+ this.itemfilter = new FeedItemFilter(properties);
+ }
+ }
+
+ public boolean hasLastUpdateFailed() {
+ return this.lastUpdateFailed;
+ }
+
+ public void setLastUpdateFailed(boolean lastUpdateFailed) {
+ this.lastUpdateFailed = lastUpdateFailed;
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
new file mode 100644
index 000000000..d04d236e4
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
@@ -0,0 +1,28 @@
+package de.danoeh.antennapod.core.feed;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class FeedEvent {
+
+ public enum Action {
+ FILTER_CHANGED
+ }
+
+ public final Action action;
+ public final long feedId;
+
+ public FeedEvent(Action action, long feedId) {
+ this.action = action;
+ this.feedId = feedId;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("feedId", feedId)
+ .toString();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index 5a4d869e7..1168c60e4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -2,6 +2,9 @@ package de.danoeh.antennapod.core.feed;
import android.net.Uri;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
@@ -60,6 +63,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
private List<Chapter> chapters;
private FeedImage image;
+ private boolean autoDownload = true;
+
public FeedItem() {
this.read = true;
this.flattrStatus = new FlattrStatus();
@@ -71,7 +76,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, boolean read,
- String itemIdentifier) {
+ String itemIdentifier, boolean autoDownload) {
this.id = id;
this.title = title;
this.link = link;
@@ -83,6 +88,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.image = image;
this.read = read;
this.itemIdentifier = itemIdentifier;
+ this.autoDownload = autoDownload;
}
/**
@@ -233,7 +239,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public boolean isRead() {
- return read || isInProgress();
+ return read;
}
public void setRead(boolean read) {
@@ -312,10 +318,10 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
@Override
public Uri getImageUri() {
- if (hasItemImageDownloaded()) {
- return image.getImageUri();
- } else if (hasMedia()) {
+ if(media != null && media.hasEmbeddedPicture()) {
return media.getImageUri();
+ } else if (hasItemImageDownloaded()) {
+ return image.getImageUri();
} else if (feed != null) {
return feed.getImageUri();
} else {
@@ -324,7 +330,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public enum State {
- NEW, IN_PROGRESS, READ, PLAYING
+ UNREAD, IN_PROGRESS, READ, PLAYING
}
public State getState() {
@@ -336,7 +342,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
return State.IN_PROGRESS;
}
}
- return (isRead() ? State.READ : State.NEW);
+ return (isRead() ? State.READ : State.UNREAD);
}
public long getFeedId() {
@@ -384,4 +390,24 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public boolean hasChapters() {
return hasChapters;
}
+
+ public void setAutoDownload(boolean autoDownload) {
+ this.autoDownload = autoDownload;
+ }
+
+ public boolean getAutoDownload() {
+ return this.autoDownload;
+ }
+
+ public boolean isAutoDownloadable() {
+ return this.hasMedia() &&
+ false == this.getMedia().isPlaying() &&
+ false == this.getMedia().isDownloaded() &&
+ this.getAutoDownload();
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
new file mode 100644
index 000000000..4ad084b39
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
@@ -0,0 +1,82 @@
+package de.danoeh.antennapod.core.feed;
+
+import android.content.Context;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.core.storage.DBReader;
+
+public class FeedItemFilter {
+
+ private final String[] properties;
+
+ private boolean hideUnplayed = false;
+ private boolean hidePaused = false;
+ private boolean hidePlayed = false;
+ private boolean hideQueued = false;
+ private boolean hideNotQueued = false;
+ private boolean hideDownloaded = false;
+ private boolean hideNotDownloaded = false;
+
+ public FeedItemFilter(String properties) {
+ this(StringUtils.split(properties, ','));
+ }
+
+ public FeedItemFilter(String[] properties) {
+ this.properties = properties;
+ for(String property : properties) {
+ // see R.arrays.feed_filter_values
+ switch(property) {
+ case "unplayed":
+ hideUnplayed = true;
+ break;
+ case "paused":
+ hidePaused = true;
+ break;
+ case "played":
+ hidePlayed = true;
+ break;
+ case "queued":
+ hideQueued = true;
+ break;
+ case "not_queued":
+ hideNotQueued = true;
+ break;
+ case "downloaded":
+ hideDownloaded = true;
+ break;
+ case "not_downloaded":
+ hideNotDownloaded = true;
+ break;
+ }
+ }
+ }
+
+ public List<FeedItem> filter(Context context, List<FeedItem> items) {
+ if(properties.length == 0) {
+ return items;
+ }
+ List<FeedItem> result = new ArrayList<FeedItem>();
+ for(FeedItem item : items) {
+ if(hideUnplayed && false == item.isRead()) continue;
+ if(hidePaused && item.getState() == FeedItem.State.IN_PROGRESS) continue;
+ if(hidePlayed && item.isRead()) continue;
+ boolean isQueued = DBReader.getQueueIDList(context).contains(item.getId());
+ if(hideQueued && isQueued) continue;
+ if(hideNotQueued && false == isQueued) continue;
+ boolean isDownloaded = item.getMedia() != null && item.getMedia().isDownloaded();
+ if(hideDownloaded && isDownloaded) continue;
+ if(hideNotDownloaded && false == isDownloaded) continue;
+ result.add(item);
+ }
+ return result;
+ }
+
+ public String[] getValues() {
+ return properties.clone();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 69e96c503..f875eb812 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -12,6 +13,7 @@ import java.util.concurrent.Callable;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.ChapterUtils;
@@ -33,6 +35,7 @@ public class FeedMedia extends FeedFile implements Playable {
private String mime_type;
private volatile FeedItem item;
private Date playbackCompletionDate;
+ private boolean hasEmbeddedPicture;
/* Used for loading item when restoring from parcel. */
private long itemID;
@@ -49,6 +52,7 @@ public class FeedMedia extends FeedFile implements Playable {
long size, String mime_type, String file_url, String download_url,
boolean downloaded, Date playbackCompletionDate, int played_duration) {
super(file_url, download_url, downloaded);
+ checkEmbeddedPicture();
this.id = id;
this.item = item;
this.duration = duration;
@@ -60,12 +64,6 @@ public class FeedMedia extends FeedFile implements Playable {
? null : (Date) playbackCompletionDate.clone();
}
- public FeedMedia(long id, FeedItem item) {
- super();
- this.id = id;
- this.item = item;
- }
-
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
@@ -146,6 +144,11 @@ public class FeedMedia extends FeedFile implements Playable {
}
+ public boolean hasAlmostEnded() {
+ int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs();
+ return this.position >= this.duration - smartMarkAsPlayedSecs * 1000;
+ }
+
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEEDMEDIA;
@@ -221,18 +224,15 @@ public class FeedMedia extends FeedFile implements Playable {
return (this.position > 0);
}
- public FeedImage getImage() {
- if (item != null) {
- return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage();
- }
- return null;
- }
-
@Override
public int describeContents() {
return 0;
}
+ public boolean hasEmbeddedPicture() {
+ return this.hasEmbeddedPicture;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
@@ -409,28 +409,45 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public Uri getImageUri() {
- final Uri feedImgUri = getFeedImageUri();
-
- if (localFileAvailable()) {
+ if (hasEmbeddedPicture) {
Uri.Builder builder = new Uri.Builder();
- builder.scheme(SCHEME_MEDIA)
- .encodedPath(getLocalMediaUrl());
- if (feedImgUri != null) {
- builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString());
- }
+ builder.scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl());
return builder.build();
- } else if (item.hasItemImageDownloaded()) {
- return item.getImage().getImageUri();
} else {
- return feedImgUri;
+ return item.getImageUri();
}
}
- private Uri getFeedImageUri() {
- if (item != null && item.getFeed() != null) {
- return item.getFeed().getImageUri();
- } else {
- return null;
+ @Override
+ public void setDownloaded(boolean downloaded) {
+ super.setDownloaded(downloaded);
+ checkEmbeddedPicture();
+ }
+
+ @Override
+ public void setFile_url(String file_url) {
+ super.setFile_url(file_url);
+ checkEmbeddedPicture();
+ }
+
+ private void checkEmbeddedPicture() {
+ if (!localFileAvailable()) {
+ hasEmbeddedPicture = false;
+ return;
+ }
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ try {
+ mmr.setDataSource(getLocalMediaUrl());
+ byte[] image = mmr.getEmbeddedPicture();
+ if(image != null) {
+ hasEmbeddedPicture = true;
+ }
+ else {
+ hasEmbeddedPicture = false;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ hasEmbeddedPicture = false;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
new file mode 100644
index 000000000..c8497f509
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
@@ -0,0 +1,51 @@
+package de.danoeh.antennapod.core.feed;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.List;
+
+public class QueueEvent {
+
+ public enum Action {
+ ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
+ }
+
+ public final Action action;
+ public final FeedItem item;
+ public final int position;
+ public final List<FeedItem> items;
+
+ public QueueEvent(Action action) {
+ this(action, null, null, -1);
+ }
+
+ public QueueEvent(Action action, FeedItem item) {
+ this(action, item, null, -1);
+ }
+
+ public QueueEvent(Action action, FeedItem item, int position) {
+ this(action, item, null, position);
+ }
+
+ public QueueEvent(Action action, List<FeedItem> items) {
+ this(action, null, items, -1);
+ }
+
+ private QueueEvent(Action action, FeedItem item, List<FeedItem> items, int position) {
+ this.action = action;
+ this.item = item;
+ this.items = items;
+ this.position = position;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("item", item)
+ .append("items", items)
+ .append("position", position)
+ .toString();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
index 4d88449d6..1a40120e2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
@@ -45,6 +45,9 @@ import javax.security.auth.x500.X500Principal;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionGetResponse;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionPostResponse;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
@@ -462,6 +465,85 @@ public class GpodnetService {
}
/**
+ * Updates the episode actions
+ * <p/>
+ * This method requires authentication.
+ *
+ * @param episodeActions Collection of episode actions.
+ * @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse}
+ * for details.
+ * @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
+ * @throws de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
+ * is an authentication error.
+ */
+ public GpodnetEpisodeActionPostResponse uploadEpisodeActions(Collection<GpodnetEpisodeAction> episodeActions)
+ throws GpodnetServiceException {
+
+ Validate.notNull(episodeActions);
+
+ String username = GpodnetPreferences.getUsername();
+
+ try {
+ URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
+ "/api/2/episodes/%s.json", username), null).toURL();
+
+ final JSONArray list = new JSONArray();
+ for(GpodnetEpisodeAction episodeAction : episodeActions) {
+ JSONObject obj = episodeAction.writeToJSONObject();
+ if(obj != null) {
+ list.put(obj);
+ }
+ }
+
+ RequestBody body = RequestBody.create(JSON, list.toString());
+ Request.Builder request = new Request.Builder().post(body).url(url);
+
+ final String response = executeRequest(request);
+ return GpodnetEpisodeActionPostResponse.fromJSONObject(response);
+ } catch (JSONException | MalformedURLException | URISyntaxException e) {
+ e.printStackTrace();
+ throw new GpodnetServiceException(e);
+ }
+
+ }
+
+ /**
+ * Returns all subscription changes of a specific device.
+ * <p/>
+ * This method requires authentication.
+ *
+ * @param timestamp A timestamp that can be used to receive all changes since a
+ * specific point in time.
+ * @throws IllegalArgumentException If username or deviceId is null.
+ * @throws GpodnetServiceAuthenticationException If there is an authentication error.
+ */
+ public GpodnetEpisodeActionGetResponse getEpisodeChanges(long timestamp) throws GpodnetServiceException {
+
+ String username = GpodnetPreferences.getUsername();
+
+ String params = String.format("since=%d", timestamp);
+ String path = String.format("/api/2/episodes/%s.json",
+ username);
+ try {
+ URL url = new URI(BASE_SCHEME, null, BASE_HOST, -1, path, params,
+ null).toURL();
+ Request.Builder request = new Request.Builder().url(url);
+
+ String response = executeRequest(request);
+ JSONObject json = new JSONObject(response);
+ return readEpisodeActionsFromJSONObject(json);
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ } catch (JSONException | MalformedURLException e) {
+ e.printStackTrace();
+ throw new GpodnetServiceException(e);
+ }
+
+ }
+
+
+ /**
* Logs in a specific user. This method must be called if any of the methods
* that require authentication is used.
*
@@ -569,7 +651,12 @@ public class GpodnetService {
Validate.notNull(body);
ByteArrayOutputStream outputStream;
- int contentLength = (int) body.contentLength();
+ int contentLength = 0;
+ try {
+ contentLength = (int) body.contentLength();
+ } catch (IOException ignore) {
+ // ignore
+ }
if (contentLength > 0) {
outputStream = new ByteArrayOutputStream(contentLength);
} else {
@@ -698,4 +785,24 @@ public class GpodnetService {
long timestamp = object.getLong("timestamp");
return new GpodnetSubscriptionChange(added, removed, timestamp);
}
+
+ private GpodnetEpisodeActionGetResponse readEpisodeActionsFromJSONObject(
+ JSONObject object) throws JSONException {
+ Validate.notNull(object);
+
+ List<GpodnetEpisodeAction> episodeActions = new ArrayList<GpodnetEpisodeAction>();
+
+ long timestamp = object.getLong("timestamp");
+ JSONArray jsonActions = object.getJSONArray("actions");
+ for(int i=0; i < jsonActions.length(); i++) {
+ JSONObject jsonAction = jsonActions.getJSONObject(i);
+ GpodnetEpisodeAction episodeAction = GpodnetEpisodeAction.readFromJSONObject(jsonAction);
+ if(episodeAction != null) {
+ episodeActions.add(episodeAction);
+ }
+ }
+ return new GpodnetEpisodeActionGetResponse(episodeActions, timestamp);
+ }
+
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
new file mode 100644
index 000000000..bd6210d13
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
@@ -0,0 +1,315 @@
+package de.danoeh.antennapod.core.gpoddernet.model;
+
+
+import android.util.Log;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.util.DateUtils;
+
+public class GpodnetEpisodeAction {
+
+ private static final String TAG = "GpodnetEpisodeAction";
+
+ public enum Action {
+ NEW, DOWNLOAD, PLAY, DELETE
+ }
+
+ private final String podcast;
+ private final String episode;
+ private final String deviceId;
+ private final Action action;
+ private final Date timestamp;
+ private final int started;
+ private final int position;
+ private final int total;
+
+ private GpodnetEpisodeAction(Builder builder) {
+ this.podcast = builder.podcast;
+ this.episode = builder.episode;
+ this.action = builder.action;
+ this.deviceId = builder.deviceId;
+ this.timestamp = builder.timestamp;
+ this.started = builder.started;
+ this.position = builder.position;
+ this.total = builder.total;
+ }
+
+ /**
+ * Creates an episode action object from a String representation. The representation includes
+ * all mandatory and optional attributes
+ *
+ * @param s String representation (output from {@link #writeToString()})
+ * @return episode action object, or null if s is invalid
+ */
+ public static GpodnetEpisodeAction readFromString(String s) {
+ String[] fields = s.split("\t");
+ if(fields.length != 8) {
+ return null;
+ }
+ String podcast = fields[0];
+ String episode = fields[1];
+ String deviceId = fields[2];
+ try {
+ Action action = Action.valueOf(fields[3]);
+ GpodnetEpisodeAction result = new Builder(podcast, episode, action)
+ .deviceId(deviceId)
+ .timestamp(new Date(Long.valueOf(fields[4])))
+ .started(Integer.valueOf(fields[5]))
+ .position(Integer.valueOf(fields[6]))
+ .total(Integer.valueOf(fields[7]))
+ .build();
+ return result;
+ } catch(IllegalArgumentException e) {
+ Log.e(TAG, "readFromString(" + s + "): " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Create an episode action object from JSON representation. Mandatory fields are "podcast",
+ * "episode" and "action".
+ *
+ * @param object JSON representation
+ * @return episode action object, or null if mandatory values are missing
+ */
+ public static GpodnetEpisodeAction readFromJSONObject(JSONObject object) {
+ String podcast = object.optString("podcast", null);
+ String episode = object.optString("episode", null);
+ String actionString = object.optString("action", null);
+ if(StringUtils.isEmpty(podcast) || StringUtils.isEmpty(episode) || StringUtils.isEmpty(actionString)) {
+ return null;
+ }
+ GpodnetEpisodeAction.Action action = GpodnetEpisodeAction.Action.valueOf(actionString.toUpperCase());
+ String deviceId = object.optString("device", "");
+ GpodnetEpisodeAction.Builder builder = new GpodnetEpisodeAction.Builder(podcast, episode, action)
+ .deviceId(deviceId);
+ String utcTimestamp = object.optString("timestamp", null);
+ if(StringUtils.isNotEmpty(utcTimestamp)) {
+ builder.timestamp(DateUtils.parse(utcTimestamp));
+ }
+ if(action == GpodnetEpisodeAction.Action.PLAY) {
+ int started = object.optInt("started", -1);
+ int position = object.optInt("position", -1);
+ int total = object.optInt("total", -1);
+ if(started >= 0 && position > 0 && total > 0) {
+ builder
+ .started(started)
+ .position(position)
+ .total(total);
+ }
+ }
+ return builder.build();
+ }
+
+ public String getPodcast() {
+ return this.podcast;
+ }
+
+ public String getEpisode() {
+ return this.episode;
+ }
+
+ public String getDeviceId() {
+ return this.deviceId;
+ }
+
+ public Action getAction() {
+ return this.action;
+ }
+
+ public String getActionString() {
+ return this.action.name().toLowerCase();
+ }
+
+ public Date getTimestamp() {
+ return this.timestamp;
+ }
+
+ /**
+ * Returns the position (in seconds) at which the client started playback
+ *
+ * @return start position (in seconds)
+ */
+ public int getStarted() {
+ return this.started;
+ }
+
+ /**
+ * Returns the position (in seconds) at which the client stopped playback
+ *
+ * @return stop position (in seconds)
+ */
+ public int getPosition() {
+ return this.position;
+ }
+
+ /**
+ * Returns the total length of the file in seconds.
+ *
+ * @return total length in seconds
+ */
+ public int getTotal() {
+ return this.total;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null) return false;
+ if(this == o) return true;
+ if(this.getClass() != o.getClass()) return false;
+ GpodnetEpisodeAction that = (GpodnetEpisodeAction)o;
+ return new EqualsBuilder()
+ .append(this.podcast, that.podcast)
+ .append(this.episode, that.episode)
+ .append(this.deviceId, that.deviceId)
+ .append(this.action, that.action)
+ .append(this.timestamp, that.timestamp)
+ .append(this.started, that.started)
+ .append(this.position, that.position)
+ .append(this.total, that.total)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(this.podcast)
+ .append(this.episode)
+ .append(this.deviceId)
+ .append(this.action)
+ .append(this.timestamp)
+ .append(this.started)
+ .append(this.position)
+ .append(this.total)
+ .toHashCode();
+ }
+
+ public String writeToString() {
+ StringBuilder result = new StringBuilder();
+ result.append(this.podcast).append("\t");
+ result.append(this.episode).append("\t");
+ result.append(this.deviceId).append("\t");
+ result.append(this.action).append("\t");
+ result.append(this.timestamp.getTime()).append("\t");
+ result.append(String.valueOf(this.started)).append("\t");
+ result.append(String.valueOf(this.position)).append("\t");
+ result.append(String.valueOf(this.total));
+ return result.toString();
+ }
+
+ /**
+ * Returns a JSON object representation of this object
+ *
+ * @return JSON object representation, or null if the object is invalid
+ */
+ public JSONObject writeToJSONObject() {
+ JSONObject obj = new JSONObject();
+ try {
+ obj.putOpt("podcast", this.podcast);
+ obj.putOpt("episode", this.episode);
+ obj.put("device", this.deviceId);
+ obj.put("action", this.getActionString());
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ obj.put("timestamp",formatter.format(this.timestamp));
+ if (this.getAction() == Action.PLAY) {
+ obj.put("started", this.started);
+ obj.put("position", this.position);
+ obj.put("total", this.total);
+ }
+ } catch(JSONException e) {
+ Log.e(TAG, "writeToJSONObject(): " + e.getMessage());
+ return null;
+ }
+ return obj;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ public static class Builder {
+
+ // mandatory
+ private final String podcast;
+ private final String episode;
+ private final Action action;
+
+ // optional
+ private String deviceId = "";
+ private Date timestamp;
+ private int started = -1;
+ private int position = -1;
+ private int total = -1;
+
+ public Builder(FeedItem item, Action action) {
+ this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action);
+ }
+
+ public Builder(String podcast, String episode, Action action) {
+ this.podcast = podcast;
+ this.episode = episode;
+ this.action = action;
+ }
+
+ public Builder deviceId(String deviceId) {
+ this.deviceId = deviceId;
+ return this;
+ }
+
+ public Builder currentDeviceId() {
+ return deviceId(GpodnetPreferences.getDeviceID());
+ }
+
+ public Builder timestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ public Builder currentTimestamp() {
+ return timestamp(new Date());
+ }
+
+ public Builder started(int seconds) {
+ if(action == Action.PLAY) {
+ this.started = seconds;
+ }
+ return this;
+ }
+
+ public Builder position(int seconds) {
+ if(action == Action.PLAY) {
+ this.position = seconds;
+ }
+ return this;
+ }
+
+ public Builder total(int seconds) {
+ if(action == Action.PLAY) {
+ this.total = seconds;
+ }
+ return this;
+ }
+
+ public GpodnetEpisodeAction build() {
+ return new GpodnetEpisodeAction(this);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
new file mode 100644
index 000000000..50420f0a3
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionGetResponse.java
@@ -0,0 +1,34 @@
+package de.danoeh.antennapod.core.gpoddernet.model;
+
+
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.List;
+
+public class GpodnetEpisodeActionGetResponse {
+
+ private final List<GpodnetEpisodeAction> episodeActions;
+ private final long timestamp;
+
+ public GpodnetEpisodeActionGetResponse(List<GpodnetEpisodeAction> episodeActions, long timestamp) {
+ Validate.notNull(episodeActions);
+ this.episodeActions = episodeActions;
+ this.timestamp = timestamp;
+ }
+
+ public List<GpodnetEpisodeAction> getEpisodeActions() {
+ return this.episodeActions;
+ }
+
+ public long getTimestamp() {
+ return this.timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
new file mode 100644
index 000000000..e06a88d5c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
@@ -0,0 +1,53 @@
+package de.danoeh.antennapod.core.gpoddernet.model;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class GpodnetEpisodeActionPostResponse {
+
+ /**
+ * timestamp/ID that can be used for requesting changes since this upload.
+ */
+ public final long timestamp;
+
+ /**
+ * URLs that should be updated. The key of the map is the original URL, the value of the map
+ * is the sanitized URL.
+ */
+ public final Map<String, String> updatedUrls;
+
+ public GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
+ this.timestamp = timestamp;
+ this.updatedUrls = updatedUrls;
+ }
+
+ /**
+ * Creates a new GpodnetUploadChangesResponse-object from a JSON object that was
+ * returned by an uploadChanges call.
+ *
+ * @throws org.json.JSONException If the method could not parse the JSONObject.
+ */
+ public static GpodnetEpisodeActionPostResponse fromJSONObject(String objectString) throws JSONException {
+ final JSONObject object = new JSONObject(objectString);
+ final long timestamp = object.getLong("timestamp");
+ Map<String, String> updatedUrls = new HashMap<String, String>();
+ JSONArray urls = object.getJSONArray("update_urls");
+ for (int i = 0; i < urls.length(); i++) {
+ JSONArray urlPair = urls.getJSONArray(i);
+ updatedUrls.put(urlPair.getString(0), urlPair.getString(1));
+ }
+ return new GpodnetEpisodeActionPostResponse(timestamp, updatedUrls);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+}
+
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
index af04df017..c3c6ce8c5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
@@ -4,14 +4,20 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
/**
@@ -28,9 +34,11 @@ public class GpodnetPreferences {
public static final String PREF_GPODNET_HOSTNAME = "prefGpodnetHostname";
- public static final String PREF_LAST_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
+ public static final String PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
+ public static final String PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_episode_actions_sync_timestamp";
public static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added";
public static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed";
+ public static final String PREF_SYNC_EPISODE_ACTIONS = "de.danoeh.antennapod.preferences.gpoddernet.sync_queued_episode_actions";
private static String username;
private static String password;
@@ -41,10 +49,14 @@ public class GpodnetPreferences {
private static Set<String> addedFeeds;
private static Set<String> removedFeeds;
+ private static List<GpodnetEpisodeAction> queuedEpisodeActions;
+
/**
* Last value returned by getSubscriptionChanges call. Will be used for all subsequent calls of getSubscriptionChanges.
*/
- private static long lastSyncTimestamp;
+ private static long lastSubscriptionSyncTimestamp;
+
+ private static long lastEpisodeActionsSyncTimeStamp;
private static boolean preferencesLoaded = false;
@@ -58,9 +70,11 @@ public class GpodnetPreferences {
username = prefs.getString(PREF_GPODNET_USERNAME, null);
password = prefs.getString(PREF_GPODNET_PASSWORD, null);
deviceID = prefs.getString(PREF_GPODNET_DEVICEID, null);
- lastSyncTimestamp = prefs.getLong(PREF_LAST_SYNC_TIMESTAMP, 0);
+ lastSubscriptionSyncTimestamp = prefs.getLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0);
+ lastEpisodeActionsSyncTimeStamp = prefs.getLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0);
addedFeeds = readListFromString(prefs.getString(PREF_SYNC_ADDED, ""));
removedFeeds = readListFromString(prefs.getString(PREF_SYNC_REMOVED, ""));
+ queuedEpisodeActions = readEpisodeActionsFromString(prefs.getString(PREF_SYNC_EPISODE_ACTIONS, ""));
hostname = checkGpodnetHostname(prefs.getString(PREF_GPODNET_HOSTNAME, GpodnetService.DEFAULT_BASE_HOST));
preferencesLoaded = true;
@@ -115,14 +129,24 @@ public class GpodnetPreferences {
writePreference(PREF_GPODNET_DEVICEID, deviceID);
}
- public static long getLastSyncTimestamp() {
+ public static long getLastSubscriptionSyncTimestamp() {
+ ensurePreferencesLoaded();
+ return lastSubscriptionSyncTimestamp;
+ }
+
+ public static void setLastSubscriptionSyncTimestamp(long timestamp) {
+ GpodnetPreferences.lastSubscriptionSyncTimestamp = timestamp;
+ writePreference(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, timestamp);
+ }
+
+ public static long getLastEpisodeActionsSyncTimestamp() {
ensurePreferencesLoaded();
- return lastSyncTimestamp;
+ return lastEpisodeActionsSyncTimeStamp;
}
- public static void setLastSyncTimestamp(long lastSyncTimestamp) {
- GpodnetPreferences.lastSyncTimestamp = lastSyncTimestamp;
- writePreference(PREF_LAST_SYNC_TIMESTAMP, lastSyncTimestamp);
+ public static void setLastEpisodeActionsSyncTimestamp(long timestamp) {
+ GpodnetPreferences.lastEpisodeActionsSyncTimeStamp = timestamp;
+ writePreference(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, timestamp);
}
public static String getHostname() {
@@ -149,7 +173,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_REMOVED, removedFeeds);
}
feedListLock.unlock();
- GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
+ GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static void addRemovedFeed(String feed) {
@@ -162,7 +186,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_ADDED, addedFeeds);
}
feedListLock.unlock();
- GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
+ GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static Set<String> getAddedFeedsCopy() {
@@ -195,7 +219,24 @@ public class GpodnetPreferences {
ensurePreferencesLoaded();
removedFeeds.removeAll(removed);
writePreference(PREF_SYNC_REMOVED, removedFeeds);
+ }
+
+ public static synchronized void enqueueEpisodeAction(GpodnetEpisodeAction action) {
+ ensurePreferencesLoaded();
+ queuedEpisodeActions.add(action);
+ writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
+ }
+ public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
+ ensurePreferencesLoaded();
+ return Collections.unmodifiableList(queuedEpisodeActions);
+ }
+
+ public static synchronized void removeQueuedEpisodeActions(Collection<GpodnetEpisodeAction> queued) {
+ ensurePreferencesLoaded();
+ queuedEpisodeActions.removeAll(queued);
+ writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
}
/**
@@ -215,7 +256,9 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_ADDED, addedFeeds);
removedFeeds.clear();
writePreference(PREF_SYNC_REMOVED, removedFeeds);
- setLastSyncTimestamp(0);
+ queuedEpisodeActions.clear();
+ writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ setLastSubscriptionSyncTimestamp(0);
}
private static Set<String> readListFromString(String s) {
@@ -235,6 +278,29 @@ public class GpodnetPreferences {
return result.toString().trim();
}
+ private static List<GpodnetEpisodeAction> readEpisodeActionsFromString(String s) {
+ String[] lines = s.split("\n");
+ List<GpodnetEpisodeAction> result = new ArrayList<GpodnetEpisodeAction>(lines.length);
+ for(String line : lines) {
+ if(StringUtils.isNotBlank(line)) {
+ GpodnetEpisodeAction action = GpodnetEpisodeAction.readFromString(line);
+ if(action != null) {
+ result.add(GpodnetEpisodeAction.readFromString(line));
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String writeEpisodeActionsToString(Collection<GpodnetEpisodeAction> c) {
+ StringBuilder result = new StringBuilder();
+ for(GpodnetEpisodeAction item : c) {
+ result.append(item.writeToString());
+ result.append("\n");
+ }
+ return result.toString();
+ }
+
private static String checkGpodnetHostname(String value) {
int startIndex = 0;
if (value.startsWith("http://")) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
index 6cb2faba5..594241573 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
@@ -16,11 +16,12 @@ import org.json.JSONException;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
@@ -33,34 +34,52 @@ import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
*/
public class UserPreferences implements
SharedPreferences.OnSharedPreferenceChangeListener {
+
public static final String IMPORT_DIR = "import/";
+
private static final String TAG = "UserPreferences";
+ // User Infercasce
+ public static final String PREF_THEME = "prefTheme";
+ public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
+ public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
+ public static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
+
+ // Queue
+ public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
+
+ // Playback
public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
- public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly";
+ public static final String PREF_AUTO_DELETE = "prefAutoDelete";
+ public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
+ private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
+ public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
+ public static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
+
+ // Network
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
- public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
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_PARALLEL_DOWNLOADS = "prefParallelDownloads";
+ public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
+ public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
+ public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
+ public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
+ public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
+
+ // Services
public static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
public static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold";
- public static final String PREF_THEME = "prefTheme";
+
+ // Other
public static final String PREF_DATA_FOLDER = "prefDataFolder";
- public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
- public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
- public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
- private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
- public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
- private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
- private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
- public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
- private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs";
- private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
- private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
- public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
+
+ // Mediaplayer
+ public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
+ private static final String PREF_REWIND_SECS = "prefRewindSecs";
+ public static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
// TODO: Make this value configurable
private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f;
@@ -70,31 +89,45 @@ public class UserPreferences implements
private static UserPreferences instance;
private final Context context;
- // Preferences
+ // User Interface
+ private int theme;
+ private List<String> hiddenDrawerItems;
+ private int notifyPriority;
+ private boolean persistNotify;
+
+ // Queue
+ private boolean enqueueAtFront;
+
+ // Playback
private boolean pauseOnHeadsetDisconnect;
private boolean unpauseOnHeadsetReconnect;
private boolean followQueue;
- private boolean downloadMediaOnWifiOnly;
+ private boolean autoDelete;
+ private int smartMarkAsPlayedSecs;
+ private String[] playbackSpeedArray;
+ private boolean pauseForFocusLoss;
+ private boolean resumeAfterCall;
+
+ // Network
private long updateInterval;
private boolean allowMobileUpdate;
- private boolean displayOnlyEpisodes;
- private boolean autoDelete;
- private boolean autoFlattr;
- private float autoFlattrPlayedDurationThreshold;
- private int theme;
+ private int parallelDownloads;
+ private int episodeCacheSize;
private boolean enableAutodownload;
- private boolean enableAutodownloadWifiFilter;
private boolean enableAutodownloadOnBattery;
+ private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
- private int parallelDownloads;
- private int episodeCacheSize;
+
+ // Services
+ private boolean autoFlattr;
+ private float autoFlattrPlayedDurationThreshold;
+
+ // Settings somewhere in the GUI
private String playbackSpeed;
- private String[] playbackSpeedArray;
- private boolean pauseForFocusLoss;
- private int seekDeltaSecs;
- private boolean isFreshInstall;
- private int notifyPriority;
- private boolean persistNotify;
+ private int fastForwardSecs;
+ private int rewindSecs;
+ private boolean queueLocked;
+
private UserPreferences(Context context) {
this.context = context;
@@ -107,8 +140,7 @@ public class UserPreferences implements
* @throws IllegalArgumentException if context is null
*/
public static void createInstance(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Creating new instance of UserPreferences");
+ Log.d(TAG, "Creating new instance of UserPreferences");
Validate.notNull(context);
instance = new UserPreferences(context);
@@ -121,47 +153,54 @@ public class UserPreferences implements
}
private void loadPreferences() {
- SharedPreferences sp = PreferenceManager
- .getDefaultSharedPreferences(context);
- EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger(
- R.integer.episode_cache_size_unlimited);
- pauseOnHeadsetDisconnect = sp.getBoolean(
- PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- unpauseOnHeadsetReconnect = sp.getBoolean(
- PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // User Interface
+ theme = readThemeValue(sp.getString(PREF_THEME, "0"));
+ if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
+ notifyPriority = NotificationCompat.PRIORITY_MAX;
+ } else {
+ notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
+ }
+ hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
+ persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+
+ // Queue
+ enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
+
+ // Playback
+ pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
+ unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
- downloadMediaOnWifiOnly = sp.getBoolean(
- PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true);
- updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL,
- "0"));
- 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);
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
- theme = readThemeValue(sp.getString(PREF_THEME, "0"));
- enableAutodownloadWifiFilter = sp.getBoolean(
- PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
+ pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
+
+ // Network
+ updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
+ allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
- PREF_EPISODE_CACHE_SIZE, "20"));
+ EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger(
+ R.integer.episode_cache_size_unlimited);
+ episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
+ enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
+ autodownloadSelectedNetworks = StringUtils.split(
+ sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+
+ // Services
+ autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
+ autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
+ PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
+
+ // MediaPlayer
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
- PREF_PLAYBACK_SPEED_ARRAY, null));
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
- seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30"));
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- }
- else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
+ rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
+ queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
}
private int readThemeValue(String valueFromPrefs) {
@@ -211,8 +250,7 @@ public class UserPreferences implements
selectedSpeeds[i] = jsonArray.getString(i);
}
} catch (JSONException e) {
- Log.e(TAG,
- "Got JSON error when trying to get speeds from JSONArray");
+ Log.e(TAG, "Got JSON error when trying to get speeds from JSONArray");
e.printStackTrace();
}
}
@@ -221,99 +259,118 @@ public class UserPreferences implements
private static void instanceAvailable() {
if (instance == null) {
- throw new IllegalStateException(
- "UserPreferences was used before being set up");
+ throw new IllegalStateException("UserPreferences was used before being set up");
}
}
- public static boolean isPauseOnHeadsetDisconnect() {
+ /**
+ * Returns theme as R.style value
+ *
+ * @return R.style.Theme_AntennaPod_Light or R.style.Theme_AntennaPod_Dark
+ */
+ public static int getTheme() {
instanceAvailable();
- return instance.pauseOnHeadsetDisconnect;
+ return instance.theme;
}
- public static boolean isUnpauseOnHeadsetReconnect() {
- instanceAvailable();
- return instance.unpauseOnHeadsetReconnect;
+ public static int getNoTitleTheme() {
+ int theme = getTheme();
+ if (theme == R.style.Theme_AntennaPod_Dark) {
+ return R.style.Theme_AntennaPod_Dark_NoTitle;
+ } else {
+ return R.style.Theme_AntennaPod_Light_NoTitle;
+ }
}
- public static boolean isFollowQueue() {
+ public static List<String> getHiddenDrawerItems() {
instanceAvailable();
- return instance.followQueue;
+ return new ArrayList<String>(instance.hiddenDrawerItems);
}
- public static boolean isDownloadMediaOnWifiOnly() {
+ /**
+ * Returns notification priority.
+ *
+ * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT
+ */
+ public static int getNotifyPriority() {
instanceAvailable();
- return instance.downloadMediaOnWifiOnly;
+ return instance.notifyPriority;
}
- public static long getUpdateInterval() {
+ /**
+ * Returns true if notifications are persistent
+ *
+ * @return {@code true} if notifications are persistent, {@code false} otherwise
+ */
+ public static boolean isPersistNotify() {
instanceAvailable();
- return instance.updateInterval;
+ return instance.persistNotify;
}
- public static boolean isAllowMobileUpdate() {
+ /**
+ * Returns {@code true} if new queue elements are added to the front
+ *
+ * @return {@code true} if new queue elements are added to the front; {@code false} otherwise
+ */
+ public static boolean enqueueAtFront() {
instanceAvailable();
- return instance.allowMobileUpdate;
+ return instance.enqueueAtFront;
}
- public static boolean isDisplayOnlyEpisodes() {
+ public static boolean isPauseOnHeadsetDisconnect() {
instanceAvailable();
- //return instance.displayOnlyEpisodes;
- return false;
+ return instance.pauseOnHeadsetDisconnect;
}
- public static boolean isAutoDelete() {
+ public static boolean isUnpauseOnHeadsetReconnect() {
instanceAvailable();
- return instance.autoDelete;
+ return instance.unpauseOnHeadsetReconnect;
}
- public static boolean isAutoFlattr() {
+
+ public static boolean isFollowQueue() {
instanceAvailable();
- return instance.autoFlattr;
+ return instance.followQueue;
}
- public static int getNotifyPriority() {
+ public static boolean isAutoDelete() {
instanceAvailable();
- return instance.notifyPriority;
+ return instance.autoDelete;
}
- public static boolean isPersistNotify() {
+ public static int getSmartMarkAsPlayedSecs() {
instanceAvailable();
- return instance.persistNotify;
+ return instance.smartMarkAsPlayedSecs;
}
+ public static boolean isAutoFlattr() {
+ instanceAvailable();
+ return instance.autoFlattr;
+ }
- /**
- * Returns the time after which an episode should be auto-flattr'd in percent of the episode's
- * duration.
- */
- public static float getAutoFlattrPlayedDurationThreshold() {
+ public static String getPlaybackSpeed() {
instanceAvailable();
- return instance.autoFlattrPlayedDurationThreshold;
+ return instance.playbackSpeed;
}
- public static int getTheme() {
+ public static String[] getPlaybackSpeedArray() {
instanceAvailable();
- return instance.theme;
+ return instance.playbackSpeedArray;
}
- public static int getNoTitleTheme() {
- int theme = getTheme();
- if (theme == R.style.Theme_AntennaPod_Dark) {
- return R.style.Theme_AntennaPod_Dark_NoTitle;
- } else {
- return R.style.Theme_AntennaPod_Light_NoTitle;
- }
+ public static boolean shouldPauseForFocusLoss() {
+ instanceAvailable();
+ return instance.pauseForFocusLoss;
}
- public static boolean isEnableAutodownloadWifiFilter() {
+ public static long getUpdateInterval() {
instanceAvailable();
- return instance.enableAutodownloadWifiFilter;
+ return instance.updateInterval;
}
- public static String[] getAutodownloadSelectedNetworks() {
+ public static boolean isAllowMobileUpdate() {
instanceAvailable();
- return instance.autodownloadSelectedNetworks;
+ return instance.allowMobileUpdate;
}
public static int getParallelDownloads() {
@@ -325,21 +382,6 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
- public static String getPlaybackSpeed() {
- instanceAvailable();
- return instance.playbackSpeed;
- }
-
- public static String[] getPlaybackSpeedArray() {
- instanceAvailable();
- return instance.playbackSpeedArray;
- }
-
- public static int getSeekDeltaMs() {
- instanceAvailable();
- return 1000 * instance.seekDeltaSecs;
- }
-
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@@ -360,89 +402,163 @@ public class UserPreferences implements
return instance.enableAutodownloadOnBattery;
}
- public static boolean shouldPauseForFocusLoss() {
+ public static boolean isEnableAutodownloadWifiFilter() {
instanceAvailable();
- return instance.pauseForFocusLoss;
+ return instance.enableAutodownloadWifiFilter;
}
- public static boolean isFreshInstall() {
+ public static int getFastFowardSecs() {
instanceAvailable();
- return instance.isFreshInstall;
+ return instance.fastForwardSecs;
+ }
+
+ public static int getRewindSecs() {
+ instanceAvailable();
+ return instance.rewindSecs;
+ }
+
+
+ /**
+ * Returns the time after which an episode should be auto-flattr'd in percent of the episode's
+ * duration.
+ */
+ public static float getAutoFlattrPlayedDurationThreshold() {
+ instanceAvailable();
+ return instance.autoFlattrPlayedDurationThreshold;
+ }
+
+ public static String[] getAutodownloadSelectedNetworks() {
+ instanceAvailable();
+ return instance.autodownloadSelectedNetworks;
+ }
+
+ public static boolean shouldResumeAfterCall() {
+ instanceAvailable();
+ return instance.resumeAfterCall;
+ }
+
+ public static boolean isQueueLocked() {
+ instanceAvailable();
+ return instance.queueLocked;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Registered change of user preferences. Key: " + key);
-
- if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) {
- downloadMediaOnWifiOnly = sp.getBoolean(
- PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true);
-
- } else if (key.equals(PREF_MOBILE_UPDATE)) {
- allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
-
- } else if (key.equals(PREF_FOLLOW_QUEUE)) {
- followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
-
- } else if (key.equals(PREF_UPDATE_INTERVAL)) {
- updateInterval = readUpdateInterval(sp.getString(
- PREF_UPDATE_INTERVAL, "0"));
- ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval);
-
- } 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);
- } else if (key.equals(PREF_THEME)) {
- theme = readThemeValue(sp.getString(PREF_THEME, ""));
- } else if (key.equals(PREF_ENABLE_AUTODL_WIFI_FILTER)) {
- enableAutodownloadWifiFilter = sp.getBoolean(
- PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- } else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) {
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
- } else if(key.equals(PREF_PARALLEL_DOWNLOADS)) {
- parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- } else if (key.equals(PREF_EPISODE_CACHE_SIZE)) {
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
- PREF_EPISODE_CACHE_SIZE, "20"));
- } else if (key.equals(PREF_ENABLE_AUTODL)) {
- enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
- } else if (key.equals(PREF_ENABLE_AUTODL_ON_BATTERY)) {
- enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
- } else if (key.equals(PREF_PLAYBACK_SPEED)) {
- playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
- PREF_PLAYBACK_SPEED_ARRAY, null));
- } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
- } else if (key.equals(PREF_SEEK_DELTA_SECS)) {
- seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30"));
- } else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) {
- pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- } else if (key.equals(PREF_UNPAUSE_ON_HEADSET_RECONNECT)) {
- unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
- } else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) {
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
- } else if (key.equals(PREF_EXPANDED_NOTIFICATION)) {
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- }
- else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- } else if (key.equals(PREF_PERSISTENT_NOTIFICATION)) {
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ Log.d(TAG, "Registered change of user preferences. Key: " + key);
+ switch(key) {
+ // User Interface
+ case PREF_THEME:
+ theme = readThemeValue(sp.getString(PREF_THEME, ""));
+ break;
+ case PREF_HIDDEN_DRAWER_ITEMS:
+ hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
+ break;
+ case PREF_EXPANDED_NOTIFICATION:
+ if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
+ notifyPriority = NotificationCompat.PRIORITY_MAX;
+ } else {
+ notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
+ }
+ break;
+ case PREF_PERSISTENT_NOTIFICATION:
+ persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ break;
+ // Queue
+ case PREF_QUEUE_ADD_TO_FRONT:
+ enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
+ break;
+ // Playback
+ case PREF_PAUSE_ON_HEADSET_DISCONNECT:
+ pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
+ break;
+ case PREF_UNPAUSE_ON_HEADSET_RECONNECT:
+ unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
+ break;
+ case PREF_FOLLOW_QUEUE:
+ followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
+ break;
+ case PREF_AUTO_DELETE:
+ autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
+ break;
+ case PREF_SMART_MARK_AS_PLAYED_SECS:
+ smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
+ break;
+ case PREF_PLAYBACK_SPEED_ARRAY:
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
+ break;
+ case PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS:
+ pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
+ break;
+ case PREF_RESUME_AFTER_CALL:
+ resumeAfterCall = sp.getBoolean(PREF_RESUME_AFTER_CALL, true);
+ break;
+ // Network
+ case PREF_UPDATE_INTERVAL:
+ updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
+ ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval);
+ break;
+ case PREF_MOBILE_UPDATE:
+ allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
+ break;
+ case PREF_PARALLEL_DOWNLOADS:
+ parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
+ break;
+ case PREF_EPISODE_CACHE_SIZE:
+ episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
+ break;
+ case PREF_ENABLE_AUTODL:
+ enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ break;
+ case PREF_ENABLE_AUTODL_ON_BATTERY:
+ enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
+ break;
+ case PREF_ENABLE_AUTODL_WIFI_FILTER:
+ enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
+ break;
+ case PREF_AUTODL_SELECTED_NETWORKS:
+ autodownloadSelectedNetworks = StringUtils.split(
+ sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ break;
+ // Services
+ case PREF_AUTO_FLATTR:
+ autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
+ break;
+ case PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD:
+ autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
+ PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
+ break;
+ // Mediaplayer
+ case PREF_PLAYBACK_SPEED:
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ break;
+ case PREF_FAST_FORWARD_SECS:
+ fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
+ break;
+ case PREF_REWIND_SECS:
+ rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
+ break;
+ case PREF_QUEUE_LOCKED:
+ queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
+ break;
+ default:
+ Log.w(TAG, "Unhandled key: " + key);
}
}
+ public static void setPrefFastForwardSecs(int secs) {
+ Log.d(TAG, "setPrefFastForwardSecs(" + secs +")");
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
+ editor.putInt(PREF_FAST_FORWARD_SECS, secs);
+ editor.commit();
+ }
+
+ public static void setPrefRewindSecs(int secs) {
+ Log.d(TAG, "setPrefRewindSecs(" + secs +")");
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
+ editor.putInt(PREF_REWIND_SECS, secs);
+ editor.commit();
+ }
+
public static void setPlaybackSpeed(String speed) {
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED, speed).apply();
@@ -502,6 +618,26 @@ public class UserPreferences implements
instance.autoFlattrPlayedDurationThreshold = autoFlattrThreshold;
}
+ public static void setHiddenDrawerItems(Context context, List<String> items) {
+ instanceAvailable();
+ instance.hiddenDrawerItems = items;
+ String str = StringUtils.join(items, ',');
+ PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext())
+ .edit()
+ .putString(PREF_HIDDEN_DRAWER_ITEMS, str)
+ .commit();
+ }
+
+ public static void setQueueLocked(boolean locked) {
+ instanceAvailable();
+ instance.queueLocked = locked;
+ PreferenceManager.getDefaultSharedPreferences(instance.context)
+ .edit()
+ .putBoolean(PREF_QUEUE_LOCKED, locked)
+ .commit();
+ }
+
+
/**
* Return the folder where the app stores all of its data. This method will
* return the standard data folder if none has been set by the user.
@@ -517,8 +653,7 @@ public class UserPreferences implements
.getDefaultSharedPreferences(context.getApplicationContext());
String strDir = prefs.getString(PREF_DATA_FOLDER, null);
if (strDir == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Using default data folder");
+ Log.d(TAG, "Using default data folder");
return context.getExternalFilesDir(type);
} else {
File dataDir = new File(strDir);
@@ -549,21 +684,18 @@ public class UserPreferences implements
if (!typeDir.exists()) {
if (dataDir.canWrite()) {
if (!typeDir.mkdir()) {
- Log.e(TAG, "Could not create data folder named "
- + type);
+ Log.e(TAG, "Could not create data folder named " + type);
return null;
}
}
}
return typeDir;
}
-
}
}
public static void setDataFolder(String dir) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Result from DirectoryChooser: " + dir);
+ Log.d(TAG, "Result from DirectoryChooser: " + dir);
instanceAvailable();
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(instance.context);
@@ -586,8 +718,7 @@ public class UserPreferences implements
Log.e(TAG, "Could not create .nomedia file");
e.printStackTrace();
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, ".nomedia file created");
+ Log.d(TAG, ".nomedia file created");
}
}
@@ -600,16 +731,13 @@ public class UserPreferences implements
IMPORT_DIR);
if (importDir != null) {
if (importDir.exists()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Import directory already exists");
+ Log.d(TAG, "Import directory already exists");
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Creating import directory");
+ Log.d(TAG, "Creating import directory");
importDir.mkdir();
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Could not access external storage.");
+ Log.d(TAG, "Could not access external storage.");
}
}
@@ -618,8 +746,7 @@ public class UserPreferences implements
*/
public static void restartUpdateAlarm(long triggerAtMillis, long intervalMillis) {
instanceAvailable();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Restarting update alarm.");
+ Log.d(TAG, "Restarting update alarm.");
AlarmManager alarmManager = (AlarmManager) instance.context
.getSystemService(Context.ALARM_SERVICE);
PendingIntent updateIntent = PendingIntent.getBroadcast(
@@ -628,11 +755,9 @@ public class UserPreferences implements
if (intervalMillis != 0) {
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis,
updateIntent);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Changed alarm to new interval");
+ Log.d(TAG, "Changed alarm to new interval");
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Automatic update was deactivated");
+ Log.d(TAG, "Automatic update was deactivated");
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index 95dc4fb07..d37f97a5f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -3,41 +3,26 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.util.NetworkUtils;
/**
* Refreshes all feeds when it receives an intent
*/
public class FeedUpdateReceiver extends BroadcastReceiver {
+
private static final String TAG = "FeedUpdateReceiver";
@Override
public void onReceive(Context context, Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received intent");
- boolean mobileUpdate = UserPreferences.isAllowMobileUpdate();
- if (mobileUpdate || connectedToWifi(context)) {
+ Log.d(TAG, "Received intent");
+ if (NetworkUtils.isDownloadAllowed(context)) {
DBTasks.refreshExpiredFeeds(context);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Blocking automatic update: no wifi available / no mobile updates allowed");
+ Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
}
}
- private boolean connectedToWifi(Context context) {
- ConnectivityManager connManager = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo mWifi = connManager
- .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
-
- return mWifi.isConnected();
- }
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
index fed0d3bc8..3f2222f42 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
@@ -9,24 +9,31 @@ import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
+import android.util.Pair;
+import java.util.Collection;
import java.util.Date;
-import java.util.LinkedList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionGetResponse;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeActionPostResponse;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetSubscriptionChange;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetUploadChangesResponse;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -43,17 +50,38 @@ public class GpodnetSyncService extends Service {
public static final String ARG_ACTION = "action";
public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
+ public static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
+ public static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
private GpodnetService service;
+ private boolean syncSubscriptions = false;
+ private boolean syncActions = false;
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
- if (action != null && action.equals(ACTION_SYNC)) {
- Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
- syncWaiterThread.restart();
+ if (action != null) {
+ switch(action) {
+ case ACTION_SYNC:
+ syncSubscriptions = true;
+ syncActions = true;
+ break;
+ case ACTION_SYNC_SUBSCRIPTIONS:
+ syncSubscriptions = true;
+ break;
+ case ACTION_SYNC_ACTIONS:
+ syncActions = true;
+ break;
+ default:
+ Log.e(TAG, "Received invalid intent: action argument is invalid");
+ }
+ if(syncSubscriptions || syncActions) {
+ Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
+ syncWaiterThread.restart();
+ }
} else {
- Log.e(TAG, "Received invalid intent: action argument is null or invalid");
+ Log.e(TAG, "Received invalid intent: action argument is null");
}
return START_FLAG_REDELIVERY;
}
@@ -61,9 +89,8 @@ public class GpodnetSyncService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
- if (BuildConfig.DEBUG) Log.d(TAG, "onDestroy");
+ Log.d(TAG, "onDestroy");
syncWaiterThread.interrupt();
-
}
@Override
@@ -79,73 +106,180 @@ public class GpodnetSyncService extends Service {
return service;
}
- private synchronized void syncChanges() {
- if (GpodnetPreferences.loggedIn() && NetworkUtils.networkAvailable(this)) {
- final long timestamp = GpodnetPreferences.getLastSyncTimestamp();
- try {
- final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
- GpodnetService service = tryLogin();
-
- if (timestamp == 0) {
- // first sync: download all subscriptions...
- GpodnetSubscriptionChange changes =
- service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), 0);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Downloaded subscription changes: " + changes);
- processSubscriptionChanges(localSubscriptions, changes);
-
- // ... then upload all local subscriptions
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Uploading subscription list: " + localSubscriptions);
- GpodnetUploadChangesResponse uploadChangesResponse =
- service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), localSubscriptions, new LinkedList<String>());
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Uploading changes response: " + uploadChangesResponse);
- GpodnetPreferences.removeAddedFeeds(localSubscriptions);
- GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy());
- GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp);
- } else {
- Set<String> added = GpodnetPreferences.getAddedFeedsCopy();
- Set<String> removed = GpodnetPreferences.getRemovedFeedsCopy();
-
- // download remote changes first...
- GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), timestamp);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
- processSubscriptionChanges(localSubscriptions, subscriptionChanges);
-
- // ... then upload changes local changes
- if (BuildConfig.DEBUG)
- Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
- added.toString(), removed));
- GpodnetUploadChangesResponse uploadChangesResponse = service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), added, removed);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Upload subscriptions response: " + uploadChangesResponse);
-
- GpodnetPreferences.removeAddedFeeds(added);
- GpodnetPreferences.removeRemovedFeeds(removed);
- GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp);
- }
- clearErrorNotifications();
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- updateErrorNotification(e);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
+
+ private synchronized void sync() {
+ if (GpodnetPreferences.loggedIn() == false || NetworkUtils.networkAvailable(this) == false) {
+ stopSelf();
+ return;
+ }
+ if(syncSubscriptions) {
+ syncSubscriptionChanges();
+ syncSubscriptions = false;
+ }
+ if(syncActions) {
+ syncEpisodeActions();
+ syncActions = false;
}
stopSelf();
}
- private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
+ private synchronized void syncSubscriptionChanges() {
+ final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
+ try {
+ final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
+ Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
+ Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
+ GpodnetService service = tryLogin();
+
+ // first sync: download all subscriptions...
+ GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
+ GpodnetPreferences.getDeviceID(), timestamp);
+ long newTimeStamp = subscriptionChanges.getTimestamp();
+
+ Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
+ processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
+
+ if(timestamp == 0) {
+ // this is this apps first sync with gpodder:
+ // only submit changes gpodder has not just sent us
+ localAdded = localSubscriptions;
+ localAdded.removeAll(subscriptionChanges.getAdded());
+ localRemoved.removeAll(subscriptionChanges.getRemoved());
+ }
+ if(localAdded.size() > 0 || localRemoved.size() > 0) {
+ Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
+ localAdded, localRemoved));
+ GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
+ GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
+ newTimeStamp = uploadResponse.timestamp;
+ Log.d(TAG, "Upload changes response: " + uploadResponse);
+ GpodnetPreferences.removeAddedFeeds(localAdded);
+ GpodnetPreferences.removeRemovedFeeds(localRemoved);
+ }
+ GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
+ clearErrorNotifications();
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ updateErrorNotification(e);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private synchronized void processSubscriptionChanges(List<String> localSubscriptions,
+ Collection<String> localAdded,
+ Collection<String> localRemoved,
+ GpodnetSubscriptionChange changes) throws DownloadRequestException {
+ // local changes are always superior to remote changes!
+ // add subscription if (1) not already subscribed and (2) not just unsubscribed
for (String downloadUrl : changes.getAdded()) {
- if (!localSubscriptions.contains(downloadUrl)) {
+ if (false == localSubscriptions.contains(downloadUrl) &&
+ false == localRemoved.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, new Date(0));
DownloadRequester.getInstance().downloadFeed(this, feed);
}
}
+ // remove subscription if not just subscribed (again)
for (String downloadUrl : changes.getRemoved()) {
- DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
+ if(false == localAdded.contains(downloadUrl)) {
+ DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
+ }
+ }
+ }
+
+ private synchronized void syncEpisodeActions() {
+ final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp();
+ Log.d(TAG, "last episode actions sync timestamp: " + timestamp);
+ try {
+ GpodnetService service = tryLogin();
+
+ // download episode actions
+ GpodnetEpisodeActionGetResponse getResponse = service.getEpisodeChanges(timestamp);
+ long lastUpdate = getResponse.getTimestamp();
+ Log.d(TAG, "Downloaded episode actions: " + getResponse);
+ List<GpodnetEpisodeAction> remoteActions = getResponse.getEpisodeActions();
+
+ List<GpodnetEpisodeAction> localActions = GpodnetPreferences.getQueuedEpisodeActions();
+ processEpisodeActions(localActions, remoteActions);
+
+ // upload local actions
+ if(localActions.size() > 0) {
+ Log.d(TAG, "Uploading episode actions: " + localActions);
+ GpodnetEpisodeActionPostResponse postResponse = service.uploadEpisodeActions(localActions);
+ lastUpdate = postResponse.timestamp;
+ Log.d(TAG, "Upload episode response: " + postResponse);
+ GpodnetPreferences.removeQueuedEpisodeActions(localActions);
+ }
+ GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(lastUpdate);
+ clearErrorNotifications();
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ updateErrorNotification(e);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
+ List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException {
+ if(remoteActions.size() == 0) {
+ return;
+ }
+ Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
+ for(GpodnetEpisodeAction action : localActions) {
+ Pair key = new Pair(action.getPodcast(), action.getEpisode());
+ GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
+ if (mostRecent == null) {
+ localMostRecentPlayAction.put(key, action);
+ } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ localMostRecentPlayAction.put(key, action);
+ }
+ }
+
+ // make sure more recent local actions are not overwritten by older remote actions
+ Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
+ for (GpodnetEpisodeAction action : remoteActions) {
+ switch (action.getAction()) {
+ case NEW:
+ FeedItem newItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
+ if(newItem != null) {
+ DBWriter.markItemRead(this, newItem, false, true);
+ } else {
+ Log.i(TAG, "Unknown feed item: " + action);
+ }
+ break;
+ case DOWNLOAD:
+ break;
+ case PLAY:
+ Pair key = new Pair(action.getPodcast(), action.getEpisode());
+ GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
+ if(localMostRecent == null ||
+ localMostRecent.getTimestamp().before(action.getTimestamp())) {
+ GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
+ if (mostRecent == null) {
+ mostRecentPlayAction.put(key, action);
+ } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ mostRecentPlayAction.put(key, action);
+ }
+ }
+ break;
+ case DELETE:
+ // NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
+ break;
+ }
+ }
+ for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
+ FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
+ if (playItem != null) {
+ FeedMedia media = playItem.getMedia();
+ media.setPosition(action.getPosition() * 1000);
+ DBWriter.setFeedMedia(this, media);
+ if(playItem.getMedia().hasAlmostEnded()) {
+ DBWriter.markItemRead(this, playItem, true, true);
+ DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
+ }
+ }
}
}
@@ -156,7 +290,7 @@ public class GpodnetSyncService extends Service {
}
private void updateErrorNotification(GpodnetServiceException exception) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Posting error notification");
+ Log.d(TAG, "Posting error notification");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
final String title;
@@ -186,7 +320,7 @@ public class GpodnetSyncService extends Service {
private WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
@Override
public void onWaitCompleted() {
- syncChanges();
+ sync();
}
};
@@ -209,7 +343,7 @@ public class GpodnetSyncService extends Service {
private void reinit() {
if (thread != null && thread.isAlive()) {
- Log.d(TAG, "Interrupting waiter thread");
+ Log.d(TAG, "Interrupting waiter thread");
thread.interrupt();
}
thread = new Thread() {
@@ -248,4 +382,20 @@ public class GpodnetSyncService extends Service {
context.startService(intent);
}
}
+
+ public static void sendSyncSubscriptionsIntent(Context context) {
+ if (GpodnetPreferences.loggedIn()) {
+ Intent intent = new Intent(context, GpodnetSyncService.class);
+ intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
+ context.startService(intent);
+ }
+ }
+
+ public static void sendSyncActionsIntent(Context context) {
+ if (GpodnetPreferences.loggedIn()) {
+ Intent intent = new Intent(context, GpodnetSyncService.class);
+ intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
+ context.startService(intent);
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 60d463178..e7b226eca 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -60,6 +60,9 @@ import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
@@ -517,9 +520,7 @@ public class DownloadService extends Service {
/**
* Creates a notification at the end of the service lifecycle to notify the
* user about the number of completed downloads. A report will only be
- * created if the number of successfully downloaded feeds is bigger than 1
- * or if there is at least one failed download which is not an image or if
- * there is at least one downloaded media file.
+ * created if there is at least one failed download excluding images
*/
private void updateReport() {
// check if report should be created
@@ -547,16 +548,16 @@ public class DownloadService extends Service {
.setTicker(
getString(R.string.download_report_title))
.setContentTitle(
- getString(R.string.download_report_title))
+ getString(R.string.download_report_content_title))
.setContentText(
String.format(
getString(R.string.download_report_content),
successfulDownloads, failedDownloads)
)
- .setSmallIcon(R.drawable.stat_notify_sync)
+ .setSmallIcon(R.drawable.stat_notify_sync_error)
.setLargeIcon(
BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync)
+ R.drawable.stat_notify_sync_error)
)
.setContentIntent(
ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this)
@@ -800,6 +801,18 @@ public class DownloadService extends Service {
// queue new media files for automatic download
for (FeedItem item : savedFeed.getItems()) {
+ if(item.getPubDate() == null) {
+ Log.d(TAG, item.toString());
+ }
+ if(item.getImage() != null && item.getImage().isDownloaded() == false) {
+ item.getImage().setOwner(item);
+ try {
+ requester.downloadImage(DownloadService.this,
+ item.getImage());
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
if (!item.isRead() && item.hasMedia() && !item.getMedia().isDownloaded()) {
newMediaFiles.add(item.getMedia().getId());
}
@@ -924,6 +937,13 @@ public class DownloadService extends Service {
if (successful) {
+ // we create a 'successful' download log if the feed's last refresh failed
+ List<DownloadStatus> log = DBReader.getFeedDownloadLog(DownloadService.this, feed);
+ if(log.size() > 0 && log.get(0).isSuccessful() == false) {
+ saveDownloadStatus(new DownloadStatus(feed,
+ feed.getHumanReadableIdentifier(), DownloadError.SUCCESS, successful,
+ reasonDetailed));
+ }
return Pair.create(request, result);
} else {
numberOfDownloads.decrementAndGet();
@@ -1040,9 +1060,11 @@ public class DownloadService extends Service {
@Override
public void run() {
- if (request.isDeleteOnFailure()) {
+ if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ DBWriter.setFeedLastUpdateFailed(DownloadService.this, request.getFeedfileId(), true);
+ } else if (request.isDeleteOnFailure()) {
Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
- } else {
+ } else {
File dest = new File(request.getDestination());
if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
Log.d(TAG, "File has been partially downloaded. Writing file url");
@@ -1166,6 +1188,15 @@ public class DownloadService extends Service {
saveDownloadStatus(status);
sendDownloadHandledIntent();
+ if(GpodnetPreferences.loggedIn()) {
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.DOWNLOAD)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
+
numberOfDownloads.decrementAndGet();
queryDownloadsAsync();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
index 7abb6df5e..40b7de170 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.service.download;
import android.util.Log;
-import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
@@ -18,19 +17,20 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Date;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URIUtil;
+import okio.ByteString;
public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader";
@@ -81,11 +81,12 @@ public class HttpDownloader extends Downloader {
if (userInfo != null) {
String[] parts = userInfo.split(":");
if (parts.length == 2) {
- String credentials = Credentials.basic(parts[0], parts[1]);
+ String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
} else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
- String credentials = Credentials.basic(request.getUsername(), request.getPassword());
+ String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
+ "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
@@ -99,13 +100,29 @@ public class HttpDownloader extends Downloader {
Response response = httpClient.newCall(httpReq.build()).execute();
responseBody = response.body();
-
String contentEncodingHeader = response.header("Content-Encoding");
-
- final boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Response code is " + response.code());
+ boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+
+ Log.d(TAG, "Response code is " + response.code());
+
+ if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoding");
+ if (userInfo != null) {
+ String[] parts = userInfo.split(":");
+ if (parts.length == 2) {
+ String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
+ httpReq.header("Authorization", credentials);
+ }
+ } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
+ String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
+ "UTF-8");
+ httpReq.header("Authorization", credentials);
+ }
+ response = httpClient.newCall(httpReq.build()).execute();
+ responseBody = response.body();
+ contentEncodingHeader = response.header("Content-Encoding");
+ isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+ }
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled");
@@ -151,22 +168,18 @@ public class HttpDownloader extends Downloader {
out = new RandomAccessFile(destination, "rw");
}
-
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
request.setStatusMsg(R.string.download_running);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Getting size of download");
+ Log.d(TAG, "Getting size of download");
request.setSize(responseBody.contentLength() + request.getSoFar());
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Size is " + request.getSize());
+ Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Free space is " + freeSpace);
+ Log.d(TAG, "Free space is " + freeSpace);
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
&& request.getSize() > freeSpace) {
@@ -174,15 +187,18 @@ public class HttpDownloader extends Downloader {
return;
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting download");
- while (!cancelled
- && (count = connection.read(buffer)) != -1) {
- out.write(buffer, 0, count);
- request.setSoFar(request.getSoFar() + count);
- request.setProgressPercent((int) (((double) request
- .getSoFar() / (double) request
- .getSize()) * 100));
+ Log.d(TAG, "Starting download");
+ try {
+ while (!cancelled
+ && (count = connection.read(buffer)) != -1) {
+ out.write(buffer, 0, count);
+ request.setSoFar(request.getSoFar() + count);
+ request.setProgressPercent((int) (((double) request
+ .getSoFar() / (double) request
+ .getSize()) * 100));
+ }
+ } catch(IOException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
}
if (cancelled) {
onCancelled();
@@ -198,6 +214,9 @@ public class HttpDownloader extends Downloader {
request.getSize()
);
return;
+ } else if(request.getSize() > 0 && request.getSoFar() == 0){
+ onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read");
+ return;
}
onSuccess();
}
@@ -226,15 +245,12 @@ public class HttpDownloader extends Downloader {
}
private void onSuccess() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Download was successful");
+ Log.d(TAG, "Download was successful");
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "Download failed");
- }
+ Log.d(TAG, "Download failed");
result.setFailed(reason, reasonDetailed);
if (request.isDeleteOnFailure()) {
cleanup();
@@ -242,8 +258,7 @@ public class HttpDownloader extends Downloader {
}
private void onCancelled() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Download was cancelled");
+ Log.d(TAG, "Download was cancelled");
result.setCancelled();
cleanup();
}
@@ -256,14 +271,23 @@ public class HttpDownloader extends Downloader {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ rc);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "cleanup() didn't delete file: does not exist.");
+ Log.d(TAG, "cleanup() didn't delete file: does not exist.");
}
}
}
+ public static String encodeCredentials(String username, String password, String charset) {
+ try {
+ String credentials = username + ":" + password;
+ byte[] bytes = credentials.getBytes(charset);
+ String encoded = ByteString.of(bytes).base64();
+ return "Basic " + encoded;
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 6f3eedcb2..3f6769ee4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -36,13 +36,15 @@ import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.List;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
@@ -167,6 +169,8 @@ public class PlaybackService extends Service {
private PlaybackServiceMediaPlayer mediaPlayer;
private PlaybackServiceTaskManager taskManager;
+ private int startPosition;
+
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
private final IBinder mBinder = new LocalBinder();
@@ -179,8 +183,7 @@ public class PlaybackService extends Service {
@Override
public boolean onUnbind(Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received onUnbind event");
+ Log.d(TAG, "Received onUnbind event");
return super.onUnbind(intent);
}
@@ -214,8 +217,7 @@ public class PlaybackService extends Service {
@Override
public void onCreate() {
super.onCreate();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service created.");
+ Log.d(TAG, "Service created.");
isRunning = true;
registerReceiver(headsetDisconnected, new IntentFilter(
@@ -242,8 +244,7 @@ public class PlaybackService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Service is about to be destroyed");
+ Log.d(TAG, "Service is about to be destroyed");
isRunning = false;
started = false;
currentMediaType = MediaType.UNKNOWN;
@@ -253,14 +254,15 @@ public class PlaybackService extends Service {
unregisterReceiver(bluetoothStateUpdated);
unregisterReceiver(audioBecomingNoisy);
unregisterReceiver(skipCurrentEpisodeReceiver);
+ unregisterReceiver(pausePlayCurrentEpisodeReceiver);
+ unregisterReceiver(pauseResumeCurrentEpisodeReceiver);
mediaPlayer.shutdown();
taskManager.shutdown();
}
@Override
public IBinder onBind(Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received onBind event");
+ Log.d(TAG, "Received onBind event");
return mBinder;
}
@@ -268,8 +270,7 @@ public class PlaybackService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "OnStartCommand called");
+ Log.d(TAG, "OnStartCommand called");
final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
if (keycode == -1 && playable == null) {
@@ -278,14 +279,12 @@ public class PlaybackService extends Service {
}
if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
+ Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
stopForeground(true);
} else {
if (keycode != -1) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received media button event");
+ Log.d(TAG, "Received media button event");
handleKeycode(keycode);
} else {
started = true;
@@ -305,8 +304,7 @@ public class PlaybackService extends Service {
* Handles media button events
*/
private void handleKeycode(int keycode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Handling keycode: " + keycode);
+ Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
switch (keycode) {
@@ -348,11 +346,11 @@ public class PlaybackService extends Service {
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- mediaPlayer.seekDelta(UserPreferences.getSeekDeltaMs());
+ mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000);
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
- mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs());
+ mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
if (status == PlayerStatus.PLAYING) {
@@ -376,8 +374,7 @@ public class PlaybackService extends Service {
* mediaplayer.
*/
public void setVideoSurface(SurfaceHolder sh) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting display");
+ Log.d(TAG, "Setting display");
mediaPlayer.setVideoSurface(sh);
}
@@ -445,6 +442,21 @@ public class PlaybackService extends Service {
}
writePlayerStatusPlaybackPreferences();
+ final Playable playable = mediaPlayer.getPSMPInfo().playable;
+
+ // Gpodder: send play action
+ if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) {
+ FeedMedia media = (FeedMedia) playable;
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
+ .currentDeviceId()
+ .currentTimestamp()
+ .started(startPosition / 1000)
+ .position(getCurrentPosition() / 1000)
+ .total(getDuration() / 1000)
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
break;
case STOPPED:
@@ -453,16 +465,15 @@ public class PlaybackService extends Service {
break;
case PLAYING:
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Audiofocus successfully requested");
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Resuming/Starting playback");
+ Log.d(TAG, "Audiofocus successfully requested");
+ Log.d(TAG, "Resuming/Starting playback");
taskManager.startPositionSaver();
taskManager.startWidgetUpdater();
writePlayerStatusPlaybackPreferences();
setupNotification(newInfo);
started = true;
+ startPosition = mediaPlayer.getPosition();
break;
case ERROR:
@@ -472,9 +483,8 @@ public class PlaybackService extends Service {
}
Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED);
- statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
+ // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
sendBroadcast(statusUpdate);
- sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED));
updateWidget();
refreshRemoteControlClientState(newInfo);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
@@ -537,11 +547,10 @@ public class PlaybackService extends Service {
};
private void endPlayback(boolean playNextEpisode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback ended");
+ Log.d(TAG, "Playback ended");
- final Playable media = mediaPlayer.getPSMPInfo().playable;
- if (media == null) {
+ final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ if (playable == null) {
Log.e(TAG, "Cannot end playback: media was null");
return;
}
@@ -551,36 +560,46 @@ public class PlaybackService extends Service {
boolean isInQueue = false;
FeedItem nextItem = null;
- if (media instanceof FeedMedia) {
- FeedItem item = ((FeedMedia) media).getItem();
+ if (playable instanceof FeedMedia) {
+ FeedMedia media = (FeedMedia) playable;
+ FeedItem item = media.getItem();
DBWriter.markItemRead(PlaybackService.this, item, true, true);
try {
final List<FeedItem> queue = taskManager.getQueue();
- isInQueue = QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId());
+ isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue);
} catch (InterruptedException e) {
e.printStackTrace();
// isInQueue remains false
}
if (isInQueue) {
- DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true);
+ DBWriter.removeQueueItem(PlaybackService.this, item, true);
}
- DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media);
+ DBWriter.addItemToPlaybackHistory(PlaybackService.this, media);
// auto-flattr if enabled
if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
}
- //Delete episode if enabled
+ // Delete episode if enabled
if(UserPreferences.isAutoDelete()) {
- DBWriter.deleteFeedMediaOfItem(PlaybackService.this, item.getMedia().getId());
-
- if(BuildConfig.DEBUG)
- Log.d(TAG, "Episode Deleted");
+ DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
+ Log.d(TAG, "Episode Deleted");
}
+ // gpodder play action
+ if(GpodnetPreferences.loggedIn()) {
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
+ .currentDeviceId()
+ .currentTimestamp()
+ .started(startPosition / 1000)
+ .position(getDuration() / 1000)
+ .total(getDuration() / 1000)
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
}
// Load next episode if previous episode was in the queue and if there
@@ -596,8 +615,7 @@ public class PlaybackService extends Service {
UserPreferences.isFollowQueue();
if (loadNextItem) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Loading next item in queue");
+ Log.d(TAG, "Loading next item in queue");
nextMedia = nextItem.getMedia();
}
final boolean prepareImmediately;
@@ -605,13 +623,10 @@ public class PlaybackService extends Service {
final boolean stream;
if (playNextEpisode) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback of next episode will start immediately.");
+ Log.d(TAG, "Playback of next episode will start immediately.");
prepareImmediately = startWhenPrepared = true;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No more episodes available to play");
-
+ Log.d(TAG, "No more episodes available to play");
prepareImmediately = startWhenPrepared = false;
stopForeground(true);
stopWidgetUpdater();
@@ -619,7 +634,7 @@ public class PlaybackService extends Service {
writePlaybackPreferencesNoMediaPlaying();
if (nextMedia != null) {
- stream = !media.localFileAvailable();
+ stream = !playable.localFileAvailable();
mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
(nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
@@ -631,8 +646,7 @@ public class PlaybackService extends Service {
}
public void setSleepTimer(long waitingTime) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
+ Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
+ " milliseconds");
taskManager.setSleepTimer(waitingTime);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
@@ -675,8 +689,7 @@ public class PlaybackService extends Service {
}
private void writePlaybackPreferences() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Writing playback preferences");
+ Log.d(TAG, "Writing playback preferences");
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()).edit();
@@ -727,8 +740,7 @@ public class PlaybackService extends Service {
}
private void writePlayerStatusPlaybackPreferences() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Writing player status playback preferences");
+ Log.d(TAG, "Writing player status playback preferences");
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()).edit();
@@ -777,8 +789,7 @@ public class PlaybackService extends Service {
@Override
protected Void doInBackground(Void... params) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting background work");
+ Log.d(TAG, "Starting background work");
if (android.os.Build.VERSION.SDK_INT >= 11) {
if (info.playable != null) {
try {
@@ -888,8 +899,7 @@ public class PlaybackService extends Service {
notification = notificationBuilder.build();
}
startForeground(NOTIFICATION_ID, notification);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Notification set up");
+ Log.d(TAG, "Notification set up");
}
}
@@ -915,18 +925,15 @@ public class PlaybackService extends Service {
float playbackSpeed = getCurrentPlaybackSpeed();
final Playable playable = mediaPlayer.getPSMPInfo().playable;
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Saving current position to " + position);
+ 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() + ((int) (deltaPlayedDuration * playbackSpeed)));
+ FeedMedia media = (FeedMedia) playable;
+ FeedItem item = media.getItem();
+ media.setPlayedDuration(media.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed)));
// Auto flattr
- if (isAutoFlattrable(m) &&
- (m.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(m.getPlayedDuration())
+ if (isAutoFlattrable(media) &&
+ (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
+ Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration())
+ " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration));
DBTasks.flattrItemIfLoggedIn(this, item);
}
@@ -1019,8 +1026,7 @@ public class PlaybackService extends Service {
editor.apply();
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "RemoteControlClient state was refreshed");
+ Log.d(TAG, "RemoteControlClient state was refreshed");
}
}
}
@@ -1063,15 +1069,12 @@ public class PlaybackService extends Service {
if (StringUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", -1);
if (state != -1) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Headset plug event. State is " + state);
+ Log.d(TAG, "Headset plug event. State is " + state);
if (state == UNPLUGGED) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Headset was unplugged during playback.");
+ Log.d(TAG, "Headset was unplugged during playback.");
pauseIfPauseOnDisconnect();
} else if (state == PLUGGED) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Headset was plugged in during playback.");
+ Log.d(TAG, "Headset was plugged in during playback.");
unpauseIfPauseOnDisconnect();
}
} else {
@@ -1088,8 +1091,7 @@ public class PlaybackService extends Service {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
int prevState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received bluetooth connection intent");
+ Log.d(TAG, "Received bluetooth connection intent");
unpauseIfPauseOnDisconnect();
}
}
@@ -1101,8 +1103,7 @@ public class PlaybackService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
// sound is about to change, eg. bluetooth -> speaker
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Pausing playback because audio is becoming noisy");
+ Log.d(TAG, "Pausing playback because audio is becoming noisy");
pauseIfPauseOnDisconnect();
}
// android.media.AUDIO_BECOMING_NOISY
@@ -1148,8 +1149,7 @@ public class PlaybackService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
if (StringUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
+ Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
mediaPlayer.endPlayback();
}
}
@@ -1159,8 +1159,7 @@ public class PlaybackService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
if (StringUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent");
+ Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent");
mediaPlayer.resume();
}
}
@@ -1170,8 +1169,7 @@ public class PlaybackService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
if (StringUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent");
+ Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent");
mediaPlayer.pause(false, false);
}
}
@@ -1231,7 +1229,26 @@ public class PlaybackService extends Service {
public void seekTo(final int t) {
+ if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING
+ && GpodnetPreferences.loggedIn()) {
+ final Playable playable = mediaPlayer.getPSMPInfo().playable;
+ if (playable instanceof FeedMedia) {
+ FeedMedia media = (FeedMedia) playable;
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
+ .currentDeviceId()
+ .currentTimestamp()
+ .started(startPosition / 1000)
+ .position(getCurrentPosition() / 1000)
+ .total(getDuration() / 1000)
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
+ }
mediaPlayer.seekTo(t);
+ if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING ) {
+ startPosition = t;
+ }
}
@@ -1270,10 +1287,9 @@ public class PlaybackService extends Service {
return mediaPlayer.getVideoSize();
}
- private boolean isAutoFlattrable(Playable p) {
- if (p != null && p instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) p;
- FeedItem item = ((FeedMedia) p).getItem();
+ private boolean isAutoFlattrable(FeedMedia media) {
+ if (media != null) {
+ FeedItem item = media.getItem();
return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred();
} else {
return false;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index b7c02011d..7a8e38c59 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -24,11 +24,13 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.playback.AudioPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -38,7 +40,7 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer;
* Manages the MediaPlayer object of the PlaybackService.
*/
public class PlaybackServiceMediaPlayer {
- public static final String TAG = "PlaybackServiceMediaPlayer";
+ public static final String TAG = "PlaybackSvcMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
@@ -91,7 +93,7 @@ public class PlaybackServiceMediaPlayer {
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Rejected execution of runnable");
+ Log.d(TAG, "Rejected execution of runnable");
}
}
);
@@ -137,7 +139,7 @@ public class PlaybackServiceMediaPlayer {
public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
Validate.notNull(playable);
- if (BuildConfig.DEBUG) Log.d(TAG, "Play media object.");
+ Log.d(TAG, "playMediaObject(...)");
executor.submit(new Runnable() {
@Override
public void run() {
@@ -164,16 +166,16 @@ public class PlaybackServiceMediaPlayer {
*/
private void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
Validate.notNull(playable);
- if (!playerLock.isHeldByCurrentThread())
+ if (!playerLock.isHeldByCurrentThread()) {
throw new IllegalStateException("method requires playerLock");
+ }
if (media != null) {
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())
&& playerStatus == PlayerStatus.PLAYING) {
// episode is already playing -> ignore method call
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
+ Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
return;
} else {
// stop playback of this episode
@@ -184,6 +186,23 @@ public class PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.PLAYING) {
setPlayerStatus(PlayerStatus.PAUSED, media);
}
+
+ // smart mark as played
+ if(media != null && media instanceof FeedMedia) {
+ FeedMedia oldMedia = (FeedMedia) media;
+ if(oldMedia.hasAlmostEnded()) {
+ Log.d(TAG, "smart mark as read");
+ FeedItem item = oldMedia.getItem();
+ DBWriter.markItemRead(context, item, true, false);
+ DBWriter.removeQueueItem(context, item, false);
+ DBWriter.addItemToPlaybackHistory(context, oldMedia);
+ if (UserPreferences.isAutoDelete()) {
+ Log.d(TAG, "Delete " + oldMedia.toString());
+ DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
+ }
+ }
+ }
+
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
}
}
@@ -281,11 +300,10 @@ public class PlaybackServiceMediaPlayer {
media.onPlaybackStart();
} else {
- if (BuildConfig.DEBUG) Log.e(TAG, "Failed to request audio focus");
+ Log.e(TAG, "Failed to request audio focus");
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
+ Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
}
}
@@ -307,8 +325,7 @@ public class PlaybackServiceMediaPlayer {
playerLock.lock();
releaseWifiLockIfNecessary();
if (playerStatus == PlayerStatus.PLAYING) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Pausing playback.");
+ Log.d(TAG, "Pausing playback.");
mediaPlayer.pause();
setPlayerStatus(PlayerStatus.PAUSED, media);
@@ -320,8 +337,7 @@ public class PlaybackServiceMediaPlayer {
reinit();
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
+ Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
}
playerLock.unlock();
@@ -342,8 +358,7 @@ public class PlaybackServiceMediaPlayer {
playerLock.lock();
if (playerStatus == PlayerStatus.INITIALIZED) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Preparing media player");
+ Log.d(TAG, "Preparing media player");
setPlayerStatus(PlayerStatus.PREPARING, media);
try {
mediaPlayer.prepare();
@@ -370,8 +385,7 @@ public class PlaybackServiceMediaPlayer {
throw new IllegalStateException("Player is not in PREPARING state");
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Resource prepared");
+ Log.d(TAG, "Resource prepared");
if (mediaType == MediaType.VIDEO) {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
@@ -383,8 +397,7 @@ public class PlaybackServiceMediaPlayer {
}
if (media.getDuration() == 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting duration of media");
+ Log.d(TAG, "Setting duration of media");
media.setDuration(mediaPlayer.getDuration());
}
setPlayerStatus(PlayerStatus.PREPARED, media);
@@ -412,8 +425,7 @@ public class PlaybackServiceMediaPlayer {
} else if (mediaPlayer != null) {
mediaPlayer.reset();
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
+ Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
}
playerLock.unlock();
}
@@ -437,15 +449,15 @@ public class PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
- if (stream) {
- // statusBeforeSeeking = playerStatus;
- // setPlayerStatus(PlayerStatus.SEEKING, media);
+ if (!stream) {
+ statusBeforeSeeking = playerStatus;
+ setPlayerStatus(PlayerStatus.SEEKING, media);
}
mediaPlayer.seekTo(t);
} else if (playerStatus == PlayerStatus.INITIALIZED) {
media.setPosition(t);
- startWhenPrepared.set(true);
+ startWhenPrepared.set(false);
prepare();
}
playerLock.unlock();
@@ -529,13 +541,15 @@ public class PlaybackServiceMediaPlayer {
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
+ || playerStatus == PlayerStatus.PREPARED
+ || playerStatus == PlayerStatus.SEEKING) {
retVal = mediaPlayer.getCurrentPosition();
} else if (media != null && media.getPosition() > 0) {
retVal = media.getPosition();
}
playerLock.unlock();
+ Log.d(TAG, "getPosition() -> " + retVal);
return retVal;
}
@@ -567,8 +581,7 @@ public class PlaybackServiceMediaPlayer {
if (media != null && media.getMediaType() == MediaType.AUDIO) {
if (mediaPlayer.canSetSpeed()) {
mediaPlayer.setPlaybackSpeed((float) speed);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Playback speed was set to " + speed);
+ Log.d(TAG, "Playback speed was set to " + speed);
callback.playbackSpeedChanged(speed);
}
}
@@ -651,8 +664,7 @@ public class PlaybackServiceMediaPlayer {
@Override
public void run() {
playerLock.lock();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Resetting video surface");
+ Log.d(TAG, "Resetting video surface");
mediaPlayer.setDisplay(null);
reinit();
playerLock.unlock();
@@ -716,7 +728,7 @@ public class PlaybackServiceMediaPlayer {
private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) {
Validate.notNull(newStatus);
- if (BuildConfig.DEBUG) Log.d(TAG, "Setting player status to " + newStatus);
+ Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
this.media = newMedia;
@@ -725,6 +737,7 @@ public class PlaybackServiceMediaPlayer {
int state;
if (playerStatus != null) {
+ Log.d(TAG, "playerStatus: " + playerStatus.toString());
switch (playerStatus) {
case PLAYING:
state = PlaybackStateCompat.STATE_PLAYING;
@@ -788,17 +801,15 @@ public class PlaybackServiceMediaPlayer {
// If there is an incoming call, playback should be paused permanently
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final int callState = (tm != null) ? tm.getCallState() : 0;
- if (BuildConfig.DEBUG) Log.d(TAG, "Call state: " + callState);
Log.i(TAG, "Call state:" + callState);
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS || callState != TelephonyManager.CALL_STATE_IDLE) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus");
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
+ Log.d(TAG, "Lost audio focus");
pause(true, false);
callback.shouldStop();
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Gained audio focus");
+ Log.d(TAG, "Gained audio focus");
if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
resume();
} else { // we ducked => raise audio level back
@@ -808,22 +819,19 @@ public class PlaybackServiceMediaPlayer {
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
if (playerStatus == PlayerStatus.PLAYING) {
if (!UserPreferences.shouldPauseForFocusLoss()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER, 0);
pausedBecauseOfTransientAudiofocusLoss = false;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
+ Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
pause(false, false);
pausedBecauseOfTransientAudiofocusLoss = true;
}
}
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
if (playerStatus == PlayerStatus.PLAYING) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
pause(false, false);
pausedBecauseOfTransientAudiofocusLoss = true;
}
@@ -873,8 +881,7 @@ public class PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.STOPPED, null);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
+ Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
}
playerLock.unlock();
@@ -1091,13 +1098,13 @@ public class PlaybackServiceMediaPlayer {
@Override
public void onFastForward() {
super.onFastForward();
- seekDelta(UserPreferences.getSeekDeltaMs());
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
}
@Override
public void onRewind() {
super.onRewind();
- seekDelta(-UserPreferences.getSeekDeltaMs());
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index 1865afa6f..fc73c9446 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -5,14 +5,23 @@ import android.util.Log;
import org.apache.commons.lang3.Validate;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.Playable;
-import java.util.List;
-import java.util.concurrent.*;
+import de.greenrobot.event.EventBus;
+
/**
* Manages the background tasks of PlaybackSerivce, i.e.
@@ -69,18 +78,13 @@ public class PlaybackServiceTaskManager {
}
});
loadQueue();
- EventDistributor.getInstance().register(eventDistributorListener);
+ EventBus.getDefault().register(this);
}
- private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
- cancelQueueLoader();
- loadQueue();
- }
- }
- };
+ public void onEvent(QueueEvent event) {
+ cancelQueueLoader();
+ loadQueue();
+ }
private synchronized boolean isQueueLoaderActive() {
return queueFuture != null && !queueFuture.isDone();
@@ -145,9 +149,9 @@ public class PlaybackServiceTaskManager {
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
- if (BuildConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
+ Log.d(TAG, "Started PositionSaver");
} else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
+ Log.d(TAG, "Call to startPositionSaver was ignored.");
}
}
@@ -312,7 +316,7 @@ public class PlaybackServiceTaskManager {
* execution of this method.
*/
public synchronized void shutdown() {
- EventDistributor.getInstance().unregister(eventDistributorListener);
+ EventBus.getDefault().unregister(this);
cancelAllTasks();
schedExecutor.shutdown();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index 0164e914b..f647fd537 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -12,7 +12,7 @@ import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.util.QueueAccess;
+import de.danoeh.antennapod.core.util.LongList;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
@@ -24,7 +24,7 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
public int performCleanup(Context context, Integer episodeNumber) {
List<FeedItem> candidates = new ArrayList<FeedItem>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
- QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
+ LongList queue = DBReader.getQueueIDList(context);
List<FeedItem> delete;
for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded()
@@ -41,10 +41,10 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
Date r = rhs.getMedia().getPlaybackCompletionDate();
if (l == null) {
- l = new Date(0);
+ l = new Date();
}
if (r == null) {
- r = new Date(0);
+ r = new Date();
}
return l.compareTo(r);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
index c5f871f48..92de1eee7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
@@ -4,10 +4,9 @@ import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -53,75 +52,53 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
- final List<FeedItem> queue = DBReader.getQueue(context);
- final List<FeedItem> unreadItems = DBReader
- .getUnreadItemsList(context);
+ List<FeedItem> candidates;
+ if(mediaIds.length > 0) {
+ candidates = DBReader.getFeedItems(context, mediaIds);
+ } else {
+ final List<FeedItem> queue = DBReader.getQueue(context);
+ final List<FeedItem> unreadItems = DBReader.getUnreadItemsList(context);
+ candidates = new ArrayList<FeedItem>(queue.size() + unreadItems.size());
+ candidates.addAll(queue);
+ for(FeedItem unreadItem : unreadItems) {
+ if(candidates.contains(unreadItem) == false) {
+ candidates.add(unreadItem);
+ }
+ }
+ }
- int undownloadedEpisodes = DBTasks.getNumberOfUndownloadedEpisodes(queue,
- unreadItems);
- int downloadedEpisodes = DBReader
- .getNumberOfDownloadedEpisodes(context);
+ // filter items that are not auto downloadable
+ Iterator<FeedItem> it = candidates.iterator();
+ while(it.hasNext()) {
+ FeedItem item = it.next();
+ if(item.isAutoDownloadable() == false) {
+ it.remove();
+ }
+ }
+
+ int autoDownloadableEpisodes = candidates.size();
+ int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes(context);
int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
- APCleanupAlgorithm.getPerformAutoCleanupArgs(context, undownloadedEpisodes));
- int episodeSpaceLeft = undownloadedEpisodes;
+ APCleanupAlgorithm.getPerformAutoCleanupArgs(context, autoDownloadableEpisodes));
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
-
- if (!cacheIsUnlimited
- && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
- + undownloadedEpisodes) {
- episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- - (downloadedEpisodes - deletedEpisodes);
+ int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
+
+ int episodeSpaceLeft;
+ if (cacheIsUnlimited ||
+ episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
+ episodeSpaceLeft = autoDownloadableEpisodes;
+ } else {
+ episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
}
- Arrays.sort(mediaIds); // sort for binary search
- final boolean ignoreMediaIds = mediaIds.length == 0;
- List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
-
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (int i = 0; i < queue.size(); i++) { // ignore playing item
- FeedItem item = queue.get(i);
- long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
- if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
- && item.hasMedia()
- && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()
- && item.getFeed().getPreferences().getAutoDownload()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
+ FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
+ .toArray(new FeedItem[episodeSpaceLeft]);
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (FeedItem item : unreadItems) {
- long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
- if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
- && item.hasMedia()
- && !item.getMedia().isDownloaded()
- && item.getFeed().getPreferences().getAutoDownload()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Enqueueing " + itemsToDownload.size()
- + " items for download");
+ Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
try {
- DBTasks.downloadFeedItems(false, context,
- itemsToDownload.toArray(new FeedItem[itemsToDownload
- .size()])
- );
+ DBTasks.downloadFeedItems(false, context, itemsToDownload);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
@@ -130,4 +107,5 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
}
};
}
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index 217e6fba5..dc24c5784 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -2,11 +2,13 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.database.Cursor;
-import android.database.SQLException;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
+
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -22,6 +24,8 @@ import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.LongIntMap;
+import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.comparator.PlaybackCompletionDateComparator;
@@ -174,8 +178,7 @@ public final class DBReader {
*/
public static List<FeedItem> getFeedItemList(Context context,
final Feed feed) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+ Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@@ -228,7 +231,9 @@ public final class DBReader {
itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0,
image,
(itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
+ itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER),
+ itemlistCursor.getInt(itemlistCursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD)) > 0
+ );
itemIds.add(String.valueOf(item.getId()));
@@ -256,8 +261,8 @@ public final class DBReader {
item.getMedia().setItem(item);
}
} while (cursor.moveToNext());
- cursor.close();
}
+ cursor.close();
}
private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) {
@@ -312,7 +317,10 @@ public final class DBReader {
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0,
new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS)),
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_IS_PAGED) > 0,
- cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK));
+ cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK),
+ cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE)),
+ cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED)) > 0
+ );
if (image != null) {
image.setOwner(feed);
@@ -327,6 +335,21 @@ public final class DBReader {
return feed;
}
+ private static DownloadStatus extractDownloadStatusFromCursorRow(final Cursor cursor) {
+ long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ long feedfileId = cursor.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
+ int feedfileType = cursor.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
+ boolean successful = cursor.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
+ int reason = cursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
+ String reasonDetailed = cursor.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
+ String title = cursor.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
+ Date completionDate = new Date(cursor.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
+
+ return new DownloadStatus(id, title, feedfileId,
+ feedfileType, successful, DownloadError.fromCode(reason), completionDate,
+ reasonDetailed);
+ }
+
private static FeedItem getMatchingItemForMedia(long itemId,
List<FeedItem> items) {
@@ -339,8 +362,7 @@ public final class DBReader {
}
static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting queue");
+ Log.d(TAG, "getQueue()");
Cursor itemlistCursor = adapter.getQueueCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
@@ -359,31 +381,48 @@ public final class DBReader {
* @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
- public static List<Long> getQueueIDList(Context context) {
+ public static LongList getQueueIDList(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- List<Long> result = getQueueIDList(adapter);
+ LongList result = getQueueIDList(adapter);
adapter.close();
return result;
}
- static List<Long> getQueueIDList(PodDBAdapter adapter) {
+ static LongList getQueueIDList(PodDBAdapter adapter) {
adapter.open();
Cursor queueCursor = adapter.getQueueIDCursor();
- List<Long> queueIds = new ArrayList<Long>(queueCursor.getCount());
+ LongList queueIds = new LongList(queueCursor.getCount());
if (queueCursor.moveToFirst()) {
do {
queueIds.add(queueCursor.getLong(0));
} while (queueCursor.moveToNext());
}
+ queueCursor.close();
return queueIds;
}
/**
+ * Return the size of the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return Size of the queue.
+ */
+ public static int getQueueSize(Context context) {
+ Log.d(TAG, "getQueueSize()");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ int size = adapter.getQueueSize();
+ adapter.close();
+ return size;
+ }
+
+ /**
* Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using
* {@link #getQueueIDList(android.content.Context)} instead.
*
@@ -392,8 +431,7 @@ public final class DBReader {
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
public static List<FeedItem> getQueue(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting queue");
+ Log.d(TAG, "getQueue()");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@@ -454,24 +492,49 @@ public final class DBReader {
}
/**
+ * Loads a list of FeedItems that are considered new.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems that are considered new.
+ */
+ public static List<FeedItem> getNewItemsList(Context context) {
+ Log.d(TAG, "getNewItemsList()");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getNewItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
+ itemlistCursor.close();
+
+ loadFeedDataOfFeedItemlist(context, items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ /**
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
*
* @param context A context that is used for opening a database connection.
* @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
* over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
*/
- public static long[] getUnreadItemIds(Context context) {
+ public static LongList getNewItemIds(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- Cursor cursor = adapter.getUnreadItemIdsCursor();
- long[] itemIds = new long[cursor.getCount()];
+ Cursor cursor = adapter.getNewItemIdsCursor();
+ LongList itemIds = new LongList(cursor.getCount());
int i = 0;
if (cursor.moveToFirst()) {
do {
- itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ itemIds.add(id);
i++;
} while (cursor.moveToNext());
}
+ cursor.close();
return itemIds;
}
@@ -551,27 +614,7 @@ public final class DBReader {
if (logCursor.moveToFirst()) {
do {
- long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
-
- long feedfileId = logCursor
- .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
- int feedfileType = logCursor
- .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
- boolean successful = logCursor
- .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
- int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
- String reasonDetailed = logCursor
- .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
- String title = logCursor
- .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
- Date completionDate = new Date(
- logCursor
- .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)
- );
- downloadLog.add(new DownloadStatus(id, title, feedfileId,
- feedfileType, successful, DownloadError.fromCode(reason), completionDate,
- reasonDetailed));
-
+ downloadLog.add(extractDownloadStatusFromCursorRow(logCursor));
} while (logCursor.moveToNext());
}
logCursor.close();
@@ -580,6 +623,60 @@ public final class DBReader {
}
/**
+ * Loads the download log for a particular feed from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feed Feed for which the download log is loaded
+ * @return A list with DownloadStatus objects that represent the feed's download log,
+ * newest events first.
+ */
+ public static List<DownloadStatus> getFeedDownloadLog(Context context, Feed feed) {
+ Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + feed.toString() + ")");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId());
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
+ * Loads the download log for a particular feed media from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media Feed media for which the download log is loaded
+ * @return A list with DownloadStatus objects that represent the feed media's download log,
+ * newest events first.
+ */
+ public static List<DownloadStatus> getFeedMediaDownloadLog(Context context, FeedMedia media) {
+ Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + media.toString() + ")");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getDownloadLog(FeedMedia.FEEDFILETYPE_FEEDMEDIA, media.getId());
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
* Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
* {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)} if only metadata about
* the FeedItems is needed.
@@ -655,7 +752,34 @@ public final class DBReader {
}
}
}
+ itemCursor.close();
return item;
+ }
+
+ static List<FeedItem> getFeedItems(final Context context, PodDBAdapter adapter, final long... itemIds) {
+
+ String[] ids = new String[itemIds.length];
+ for(int i = 0; i < itemIds.length; i++) {
+ long itemId = itemIds[i];
+ ids[i] = Long.toString(itemId);
+ }
+
+ List<FeedItem> result;
+
+ Cursor itemCursor = adapter.getFeedItemCursor(ids);
+ if (itemCursor.moveToFirst()) {
+ result = extractItemlistFromCursor(adapter, itemCursor);
+ loadFeedDataOfFeedItemlist(context, result);
+ for(FeedItem item : result) {
+ if (item.hasChapters()) {
+ loadChaptersOfFeedItem(adapter, item);
+ }
+ }
+ } else {
+ result = Collections.emptyList();
+ }
+ itemCursor.close();
+ return result;
}
@@ -677,7 +801,96 @@ public final class DBReader {
FeedItem item = getFeedItem(context, itemId, adapter);
adapter.close();
return item;
+ }
+
+ static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
+ Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
+ FeedItem item = null;
+ Cursor itemCursor = adapter.getFeedItemCursor(podcastUrl, episodeUrl);
+ if (itemCursor.moveToFirst()) {
+ List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
+ if (list.size() > 0) {
+ item = list.get(0);
+ loadFeedDataOfFeedItemlist(context, list);
+ if (item.hasChapters()) {
+ loadChaptersOfFeedItem(adapter, item);
+ }
+ }
+ }
+ itemCursor.close();
+ return item;
+ }
+
+ /**
+ * Loads specific FeedItems from the database. This method canbe used for loading more
+ * than one FeedItem
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemIds The IDs of the FeedItems
+ * @return The FeedItems or an empty list if none of the FeedItems could be found. All FeedComponent-attributes
+ * as well as chapter marks of the FeedItems will also be loaded from the database.
+ */
+ public static List<FeedItem> getFeedItems(final Context context, final long... itemIds) {
+ Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ","));
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> items = getFeedItems(context, adapter, itemIds);
+ adapter.close();
+ return items;
+ }
+
+
+ /**
+ * Returns credentials based on image URL
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param imageUrl The URL of the image
+ * @return Credentials in format "<Username>:<Password>", empty String if no authorization given
+ */
+ public static String getImageAuthentication(final Context context, final String imageUrl) {
+ Log.d(TAG, "Loading credentials for image with URL " + imageUrl);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ String credentials = getImageAuthentication(context, imageUrl, adapter);
+ adapter.close();
+ return credentials;
+
+ }
+
+ static String getImageAuthentication(final Context context, final String imageUrl, PodDBAdapter adapter) {
+ String credentials = null;
+ Cursor cursor = adapter.getImageAuthenticationCursor(imageUrl);
+ try {
+ if (cursor.moveToFirst()) {
+ String username = cursor.getString(0);
+ String password = cursor.getString(1);
+ return username + ":" + password;
+ }
+ return "";
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Loads a specific FeedItem from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param podcastUrl the corresponding feed's url
+ * @param episodeUrl the feed item's url
+ * @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
+ * as well as chapter marks of the FeedItem will also be loaded from the database.
+ */
+ public static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl) {
+ Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ FeedItem item = getFeedItem(context, podcastUrl, episodeUrl, adapter);
+ adapter.close();
+ return item;
}
/**
@@ -698,6 +911,7 @@ public final class DBReader {
item.setDescription(description);
item.setContentEncoded(contentEncoded);
}
+ extraCursor.close();
adapter.close();
}
@@ -778,10 +992,24 @@ public final class DBReader {
* @param context A context that is used for opening a database connection.
* @return The number of unread items.
*/
- public static int getNumberOfUnreadItems(final Context context) {
+ public static int getNumberOfNewItems(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- final int result = adapter.getNumberOfUnreadItems();
+ final int result = adapter.getNumberOfNewItems();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Returns a map containing the number of unread items per feed
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of unread items per feed.
+ */
+ public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
adapter.close();
return result;
}
@@ -910,9 +1138,31 @@ public final class DBReader {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> feeds = getFeedList(adapter);
+ long[] feedIds = new long[feeds.size()];
+ for(int i=0; i < feeds.size(); i++) {
+ feedIds[i] = feeds.get(i).getId();
+ }
+ final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds);
+ Collections.sort(feeds, new Comparator<Feed>() {
+ @Override
+ public int compare(Feed lhs, Feed rhs) {
+ long numUnreadLhs = numUnreadFeedItems.get(lhs.getId());
+ Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items");
+ long numUnreadRhs = numUnreadFeedItems.get(rhs.getId());
+ Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items");
+ if(numUnreadLhs > numUnreadRhs) {
+ // reverse natural order: podcast with most unplayed episodes first
+ return -1;
+ } else if(numUnreadLhs == numUnreadRhs) {
+ return lhs.getTitle().compareTo(rhs.getTitle());
+ } else {
+ return 1;
+ }
+ }
+ });
int queueSize = adapter.getQueueSize();
- int numUnreadItems = adapter.getNumberOfUnreadItems();
- NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
+ int numNewItems = adapter.getNumberOfNewItems();
+ NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numUnreadFeedItems);
adapter.close();
return result;
}
@@ -920,12 +1170,15 @@ public final class DBReader {
public static class NavDrawerData {
public List<Feed> feeds;
public int queueSize;
- public int numUnreadItems;
+ public int numNewItems;
+ public LongIntMap numUnreadFeedItems;
- public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) {
+ public NavDrawerData(List<Feed> feeds, int queueSize, int numNewItems,
+ LongIntMap numUnreadFeedItems) {
this.feeds = feeds;
this.queueSize = queueSize;
- this.numUnreadItems = numUnreadItems;
+ this.numNewItems = numNewItems;
+ this.numUnreadFeedItems = numUnreadFeedItems;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index 8dd6ddea7..defce5930 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -20,7 +20,6 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher;
@@ -34,7 +33,7 @@ import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.DownloadError;
-import de.danoeh.antennapod.core.util.QueueAccess;
+import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
@@ -171,10 +170,10 @@ public final class DBTasks {
isRefreshing.set(false);
if (FlattrUtils.hasToken()) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things.");
+ Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
- if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status.");
+ Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
@@ -185,9 +184,7 @@ public final class DBTasks {
}
}.start();
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Ignoring request to refresh all feeds: Refresh lock is locked");
+ Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
}
}
@@ -223,8 +220,7 @@ public final class DBTasks {
* @param context Used for DB access.
*/
public static void refreshExpiredFeeds(final Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Refreshing expired feeds");
+ Log.d(TAG, "Refreshing expired feeds");
new Thread() {
public void run() {
@@ -306,15 +302,17 @@ public final class DBTasks {
*/
public static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
+ Log.d(TAG, "id " + feed.getId());
refreshFeed(context, feed, false);
}
private static void refreshFeed(Context context, Feed feed, boolean loadAllPages) throws DownloadRequestException {
Feed f;
+ Date lastUpdate = feed.hasLastUpdateFailed() ? new Date(0) : feed.getLastUpdate();
if (feed.getPreferences() == null) {
- f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle());
+ f = new Feed(feed.getDownload_url(), lastUpdate, feed.getTitle());
} else {
- f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle(),
+ f = new Feed(feed.getDownload_url(), lastUpdate, feed.getTitle(),
feed.getPreferences().getUsername(), feed.getPreferences().getPassword());
}
f.setId(feed.getId());
@@ -428,25 +426,6 @@ public final class DBTasks {
}
}
- static int getNumberOfUndownloadedEpisodes(
- final List<FeedItem> queue, final List<FeedItem> unreadItems) {
- int counter = 0;
- for (FeedItem item : queue) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()
- && item.getFeed().getPreferences().getAutoDownload()) {
- counter++;
- }
- }
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && item.getFeed().getPreferences().getAutoDownload()) {
- counter++;
- }
- }
- return counter;
- }
-
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
@@ -479,14 +458,6 @@ public final class DBTasks {
}
/**
- * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
- */
- public static void enqueueAllNewItems(final Context context) {
- long[] unreadItems = DBReader.getUnreadItemIds(context);
- DBWriter.addQueueItem(context, unreadItems);
- }
-
- /**
* Returns the successor of a FeedItem in the queue.
*
* @param context Used for accessing the DB.
@@ -524,8 +495,8 @@ public final class DBTasks {
* @param feedItemId ID of the FeedItem
*/
public static boolean isInQueue(Context context, final long feedItemId) {
- List<Long> queue = DBReader.getQueueIDList(context);
- return QueueAccess.IDListAccess(queue).contains(feedItemId);
+ LongList queue = DBReader.getQueueIDList(context);
+ return queue.contains(feedItemId);
}
private static Feed searchFeedByIdentifyingValueOrID(Context context, PodDBAdapter adapter,
@@ -599,8 +570,7 @@ public final class DBTasks {
newFeedsList.add(newFeed);
resultFeeds[feedIdx] = newFeed;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one.");
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
@@ -608,21 +578,17 @@ public final class DBTasks {
final boolean markNewItemsAsUnread;
if (newFeed.getPageNr() == savedFeed.getPageNr()) {
if (savedFeed.compareWithOther(newFeed)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Feed has updated attribute values. Updating old feed's attributes");
+ Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes");
savedFeed.updateFromOther(newFeed);
}
markNewItemsAsUnread = true;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "New feed has a higher page number. Merging without marking as unread");
+ Log.d(TAG, "New feed has a higher page number. Merging without marking as unread");
markNewItemsAsUnread = false;
savedFeed.setNextPageLink(newFeed.getNextPageLink());
}
if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
+ Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
savedFeed.getPreferences().updateFromOther(newFeed.getPreferences());
}
// Look for new or updated Items
@@ -634,6 +600,7 @@ public final class DBTasks {
// item is new
final int i = idx;
item.setFeed(savedFeed);
+ item.setAutoDownload(savedFeed.getPreferences().getAutoDownload());
savedFeed.getItems().add(i, item);
if (markNewItemsAsUnread) {
item.setRead(false);
@@ -645,6 +612,7 @@ public final class DBTasks {
// update attributes
savedFeed.setLastUpdate(newFeed.getLastUpdate());
savedFeed.setType(newFeed.getType());
+ savedFeed.setLastUpdateFailed(false);
updatedFeedsList.add(savedFeed);
resultFeeds[feedIdx] = savedFeed;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index c5bf89533..fe5d0dfd3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -7,6 +7,7 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.util.Log;
+
import org.shredzone.flattr4j.model.Flattr;
import java.io.File;
@@ -28,19 +29,23 @@ import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.QueueEvent;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
-import de.danoeh.antennapod.core.util.QueueAccess;
+import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing;
+import de.greenrobot.event.EventBus;
/**
* Provides methods for writing data to AntennaPod's database.
@@ -120,10 +125,18 @@ public class DBWriter {
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
}
}
+ // Gpodder: queue delete action for synchronization
+ if(GpodnetPreferences.loggedIn()) {
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleting File. Result: " + result);
- EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ Log.d(TAG, "Deleting File. Result: " + result);
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.DELETED_MEDIA, media.getItem()));
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
}
@@ -337,36 +350,20 @@ public class DBWriter {
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- final List<FeedItem> queue = DBReader
- .getQueue(context, adapter);
+ final List<FeedItem> queue = DBReader.getQueue(context, adapter);
FeedItem item = null;
if (queue != null) {
- boolean queueModified = false;
- boolean unreadItemsModified = false;
-
if (!itemListContains(queue, itemId)) {
item = DBReader.getFeedItem(context, itemId);
if (item != null) {
queue.add(index, item);
- queueModified = true;
- if (!item.isRead()) {
- item.setRead(true);
- unreadItemsModified = true;
- }
+ adapter.setQueue(queue);
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
}
}
- if (queueModified) {
- adapter.setQueue(queue);
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
- }
- if (unreadItemsModified && item != null) {
- adapter.setSingleFeedItem(item);
- EventDistributor.getInstance()
- .sendUnreadItemsUpdateBroadcast();
- }
}
+
adapter.close();
if (performAutoDownload) {
DBTasks.autodownloadUndownloadedItems(context);
@@ -407,33 +404,21 @@ public class DBWriter {
if (item != null) {
// add item to either front ot back of queue
- boolean addToFront = PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(UserPreferences.PREF_QUEUE_ADD_TO_FRONT, false);
+ boolean addToFront = UserPreferences.enqueueAtFront();
if(addToFront){
queue.add(0, item);
- }else{
+ } else {
queue.add(item);
}
queueModified = true;
- if (!item.isRead()) {
- item.setRead(true);
- itemsToSave.add(item);
- unreadItemsModified = true;
- }
}
}
}
if (queueModified) {
adapter.setQueue(queue);
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
- }
- if (unreadItemsModified) {
- adapter.setFeedItemlist(itemsToSave);
- EventDistributor.getInstance()
- .sendUnreadItemsUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
}
}
adapter.close();
@@ -459,7 +444,7 @@ public class DBWriter {
adapter.clearQueue();
adapter.close();
- EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.CLEARED));
}
});
}
@@ -468,34 +453,25 @@ public class DBWriter {
* Removes a FeedItem object from the queue.
*
* @param context A context that is used for opening a database connection.
- * @param itemId ID of the FeedItem that should be removed.
+ * @param item FeedItem that should be removed.
* @param performAutoDownload true if an auto-download process should be started after the operation.
*/
public static Future<?> removeQueueItem(final Context context,
- final long itemId, final boolean performAutoDownload) {
+ final FeedItem item, final boolean performAutoDownload) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- final List<FeedItem> queue = DBReader
- .getQueue(context, adapter);
- FeedItem item = null;
+ final List<FeedItem> queue = DBReader.getQueue(context, adapter);
if (queue != null) {
- boolean queueModified = false;
- QueueAccess queueAccess = QueueAccess.ItemListAccess(queue);
- if (queueAccess.contains(itemId)) {
- item = DBReader.getFeedItem(context, itemId);
- if (item != null) {
- queueModified = queueAccess.remove(itemId);
- }
- }
- if (queueModified) {
+ int position = queue.indexOf(item);
+ if(position >= 0) {
+ queue.remove(position);
adapter.setQueue(queue);
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item, position));
} else {
Log.w(TAG, "Queue was not modified by call to removeQueueItem");
}
@@ -523,16 +499,13 @@ public class DBWriter {
return dbExec.submit(new Runnable() {
@Override
public void run() {
- List<Long> queueIdList = DBReader.getQueueIDList(context);
- int currentLocation = 0;
- for (long id : queueIdList) {
- if (id == itemId) {
- moveQueueItemHelper(context, currentLocation, 0, broadcastUpdate);
- return;
- }
- currentLocation++;
+ LongList queueIdList = DBReader.getQueueIDList(context);
+ int index = queueIdList.indexOf(itemId);
+ if (index >=0) {
+ moveQueueItemHelper(context, index, 0, broadcastUpdate);
+ } else {
+ Log.e(TAG, "moveQueueItemToTop: item not found");
}
- Log.e(TAG, "moveQueueItemToTop: item not found");
}
});
}
@@ -550,17 +523,14 @@ public class DBWriter {
return dbExec.submit(new Runnable() {
@Override
public void run() {
- List<Long> queueIdList = DBReader.getQueueIDList(context);
- int currentLocation = 0;
- for (long id : queueIdList) {
- if (id == itemId) {
- moveQueueItemHelper(context, currentLocation, queueIdList.size() - 1,
- broadcastUpdate);
- return;
- }
- currentLocation++;
+ LongList queueIdList = DBReader.getQueueIDList(context);
+ int index = queueIdList.indexOf(itemId);
+ if(index >= 0) {
+ moveQueueItemHelper(context, index, queueIdList.size() - 1,
+ broadcastUpdate);
+ } else {
+ Log.e(TAG, "moveQueueItemToBottom: item not found");
}
- Log.e(TAG, "moveQueueItemToBottom: item not found");
}
});
}
@@ -614,8 +584,7 @@ public class DBWriter {
adapter.setQueue(queue);
if (broadcastUpdate) {
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.MOVED, item, to));
}
}
@@ -628,6 +597,19 @@ public class DBWriter {
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
*
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem
+ * @param read New value of the 'read'-attribute
+ */
+ public static Future<?> markItemRead(final Context context, final long itemId,
+ final boolean read) {
+ return markItemRead(context, itemId, read, 0, false);
+ }
+
+
+ /**
+ * Sets the 'read'-attribute of a FeedItem to the specified value.
+ *
* @param context A context that is used for opening a database connection.
* @param item The FeedItem object
* @param read New value of the 'read'-attribute
@@ -639,18 +621,6 @@ public class DBWriter {
return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition);
}
- /**
- * Sets the 'read'-attribute of a FeedItem to the specified value.
- *
- * @param context A context that is used for opening a database connection.
- * @param itemId ID of the FeedItem
- * @param read New value of the 'read'-attribute
- */
- public static Future<?> markItemRead(final Context context, final long itemId,
- final boolean read) {
- return markItemRead(context, itemId, read, 0, false);
- }
-
private static Future<?> markItemRead(final Context context, final long itemId,
final boolean read, final long mediaId,
final boolean resetMediaPosition) {
@@ -942,6 +912,26 @@ public class DBWriter {
}
/**
+ * Saves if a feed's last update failed
+ *
+ * @param lastUpdateFailed true if last update failed
+ */
+ public static Future<?> setFeedLastUpdateFailed(final Context context,
+ final long feedId,
+ final boolean lastUpdateFailed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
* format an url for querying the database
* (postfix a / and apply percent-encoding)
*/
@@ -1036,8 +1026,7 @@ public class DBWriter {
Collections.sort(queue, comparator);
adapter.setQueue(queue);
if (broadcastUpdate) {
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.SORTED));
}
} else {
Log.e(TAG, "sortQueue: Could not load queue");
@@ -1046,4 +1035,52 @@ public class DBWriter {
}
});
}
+
+ /**
+ * Sets the 'auto_download'-attribute of specific FeedItem.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedItem FeedItem.
+ */
+ public static Future<?> setFeedItemAutoDownload(final Context context, final FeedItem feedItem,
+ final boolean autoDownload) {
+ Log.d(TAG, "FeedItem[id=" + feedItem.getId() + "] SET auto_download " + autoDownload);
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemAutoDownload(feedItem, autoDownload);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Set filter of the feed
+ *
+ * @param context Used for opening a database connection.
+ * @param feedId The feed's ID
+ * @param filterValues Values that represent properties to filter by
+ */
+ public static Future<?> setFeedItemsFilter(final Context context, final long feedId,
+ final List<String> filterValues) {
+ Log.d(TAG, "setFeedFilter");
+
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemFilter(feedId, filterValues);
+ adapter.close();
+ EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId));
+ }
+ });
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index f72858adc..4780098e0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -11,6 +11,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import java.util.Arrays;
@@ -26,8 +27,11 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
+;
+
// TODO Remove media column from feeditem table
/**
@@ -149,6 +153,8 @@ public class PodDBAdapter {
public static final String KEY_PASSWORD = "password";
public static final String KEY_IS_PAGED = "is_paged";
public static final String KEY_NEXT_PAGE_LINK = "next_page_link";
+ public static final String KEY_HIDE = "hide";
+ public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@@ -175,8 +181,9 @@ public class PodDBAdapter {
+ KEY_USERNAME + " TEXT,"
+ KEY_PASSWORD + " TEXT,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
- + KEY_NEXT_PAGE_LINK + " TEXT)";
-
+ + KEY_NEXT_PAGE_LINK + " TEXT,"
+ + KEY_HIDE + " TEXT,"
+ + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)";
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -186,7 +193,8 @@ public class PodDBAdapter {
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER,"
- + KEY_IMAGE + " INTEGER)";
+ + KEY_IMAGE + " INTEGER,"
+ + KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -200,7 +208,8 @@ public class PodDBAdapter {
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
- + KEY_PLAYED_DURATION + " INTEGER)";
+ + KEY_PLAYED_DURATION + " INTEGER,"
+ + KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@@ -218,6 +227,28 @@ public class PodDBAdapter {
+ " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
+ // SQL Statements for creating indexes
+ public static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_FEED + ")";
+
+ public static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_IMAGE + ")";
+
+ public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ + KEY_FEEDITEM + ")";
+
+ public static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ + KEY_FEEDITEM + ")";
+
+ public static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ + KEY_FEEDITEM + ")";
+
+
private SQLiteDatabase db;
private final Context context;
private PodDBHelper helper;
@@ -246,6 +277,8 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK,
TABLE_NAME_FEEDS + "." + KEY_USERNAME,
TABLE_NAME_FEEDS + "." + KEY_PASSWORD,
+ TABLE_NAME_FEEDS + "." + KEY_HIDE,
+ TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
};
// column indices for FEED_SEL_STD
@@ -270,7 +303,6 @@ public class PodDBAdapter {
public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 18;
public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 19;
-
/**
* Select all columns from the feeditems-table except description and
* content-encoded.
@@ -286,7 +318,9 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
- TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE};
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
+ };
/**
* Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries.
@@ -400,17 +434,20 @@ public class PodDBAdapter {
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
+ if(feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) {
+ values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter().getValues(), ","));
+ } else {
+ values.put(KEY_HIDE, "");
+ }
+ values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed());
if (feed.getId() == 0) {
// Create new entry
- if (BuildConfig.DEBUG)
- Log.d(this.toString(), "Inserting new Feed into db");
+ Log.d(this.toString(), "Inserting new Feed into db");
feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
} else {
- if (BuildConfig.DEBUG)
- Log.d(this.toString(), "Updating existing Feed in db");
+ Log.d(this.toString(), "Updating existing Feed in db");
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())});
-
}
return feed.getId();
}
@@ -426,6 +463,13 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())});
}
+ public void setFeedItemFilter(long feedId, List<String> filterValues) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_HIDE, StringUtils.join(filterValues, ","));
+ Log.d(TAG, StringUtils.join(filterValues, ","));
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
+ }
+
/**
* Inserts or updates an image entry
*
@@ -696,6 +740,7 @@ public class PodDBAdapter {
values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters());
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
+ values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload());
if (item.hasItemImage()) {
if (item.getImage().getId() == 0) {
setImage(item.getImage());
@@ -766,6 +811,13 @@ public class PodDBAdapter {
}
}
+ public void setFeedLastUpdateFailed(long feedId, boolean failed) {
+ final String sql = "UPDATE " + TABLE_NAME_FEEDS
+ + " SET " + KEY_LAST_UPDATE_FAILED+ "=" + (failed ? "1" : "0")
+ + " WHERE " + KEY_ID + "="+ feedId;
+ db.execSQL(sql);
+ }
+
/**
* Inserts or updates a download status.
*/
@@ -787,6 +839,13 @@ public class PodDBAdapter {
return status.getId();
}
+ public void setFeedItemAutoDownload(FeedItem feedItem, boolean autoDownload) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_AUTO_DOWNLOAD, autoDownload);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
+ new String[] { String.valueOf(feedItem.getId()) } );
+ }
+
public long getDownloadLogSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG);
Cursor result = db.rawQuery(query, null);
@@ -977,6 +1036,14 @@ public class PodDBAdapter {
return c;
}
+ public final Cursor getDownloadLog(final int feedFileType, final long feedFileId) {
+ final String query = "SELECT * FROM " + TABLE_NAME_DOWNLOAD_LOG +
+ " WHERE " + KEY_FEEDFILE + "=" + feedFileId + " AND " + KEY_FEEDFILETYPE + "=" + feedFileType
+ + " ORDER BY " + KEY_ID + " DESC";
+ Cursor c = db.rawQuery(query, null);
+ return c;
+ }
+
public final Cursor getDownloadLogCursor(final int limit) {
Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit);
@@ -1021,11 +1088,43 @@ public class PodDBAdapter {
return c;
}
- public final Cursor getUnreadItemIdsCursor() {
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
- KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
- return c;
+ public final Cursor getNewItemIdsCursor() {
+ final String query = "SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
+ return db.rawQuery(query, null);
+ }
+ /**
+ * Returns a cursor which contains all feed items that are considered new.
+ * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
+ */
+ public final Cursor getNewItemsCursor() {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL" // not in queue
+ + " ORDER BY " + KEY_PUBDATE + " DESC";
+ Cursor c = db.rawQuery(query, null);
+ return c;
}
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
@@ -1120,7 +1219,11 @@ public class PodDBAdapter {
return c;
}
- public final Cursor getFeedItemCursor(final String... ids) {
+ public final Cursor getFeedItemCursor(final String id) {
+ return getFeedItemCursor(new String[]{id});
+ }
+
+ public final Cursor getFeedItemCursor(final String[] ids) {
if (ids.length > IN_OPERATOR_MAXIMUM) {
throw new IllegalArgumentException(
"number of IDs must not be larger than "
@@ -1133,6 +1236,29 @@ public class PodDBAdapter {
}
+ public final Cursor getFeedItemCursor(final String podcastUrl, final String episodeUrl) {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " +
+ TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" +
+ TABLE_NAME_FEEDS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + "='" +
+ episodeUrl + "' AND " + TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + "='" + podcastUrl + "'";
+ return db.rawQuery(query, null);
+ }
+
+ public Cursor getImageAuthenticationCursor(final String imageUrl) {
+ final String query = "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM "
+ + TABLE_NAME_FEED_IMAGES + " INNER JOIN " + TABLE_NAME_FEEDS + " ON " +
+ TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE + " WHERE "
+ + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "' UNION SELECT "
+ + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES + " INNER JOIN "
+ + TABLE_NAME_FEED_ITEMS + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE + " INNER JOIN " + TABLE_NAME_FEEDS + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID + " WHERE "
+ + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "'";
+ Log.d(TAG, "Query: " + query);
+ return db.rawQuery(query, null);
+ }
+
public int getQueueSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
Cursor c = db.rawQuery(query, null);
@@ -1144,9 +1270,20 @@ public class PodDBAdapter {
return result;
}
- public final int getNumberOfUnreadItems() {
- final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
- " WHERE " + KEY_READ + " = 0";
+ public final int getNumberOfNewItems() {
+ final String query = "SELECT COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ")"
+ +" FROM " + TABLE_NAME_FEED_ITEMS
+ + " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
@@ -1156,6 +1293,25 @@ public class PodDBAdapter {
return result;
}
+ public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
+ final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") "
+ + " AND " + KEY_READ + " = 0"
+ + " GROUP BY " + KEY_FEED;
+ Cursor c = db.rawQuery(query, null);
+ LongIntMap result = new LongIntMap(c.getCount());
+ if (c.moveToFirst()) {
+ do {
+ long feedId = c.getLong(0);
+ int count = c.getInt(1);
+ result.put(feedId, count);
+ } while(c.moveToNext());
+ }
+ c.close();
+ return result;
+ }
+
public final int getNumberOfDownloadedEpisodes() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
" WHERE " + KEY_DOWNLOADED + " > 0";
@@ -1317,6 +1473,13 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
+
+ db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
index 099593eed..23f76186b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSDublinCore.java
@@ -3,7 +3,7 @@ package de.danoeh.antennapod.core.syndication.namespace;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
-import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
+import de.danoeh.antennapod.core.util.DateUtils;
public class NSDublinCore extends Namespace {
private static final String TAG = "NSDublinCore";
@@ -30,7 +30,7 @@ public class NSDublinCore extends Namespace {
String second = secondElement.getName();
if (top.equals(DATE) && second.equals(ITEM)) {
state.getCurrentItem().setPubDate(
- SyndDateUtils.parseISO8601Date(content));
+ DateUtils.parse(content));
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
index 0ca261a0e..6455332be 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
@@ -1,14 +1,16 @@
package de.danoeh.antennapod.core.syndication.namespace;
import android.util.Log;
+
+import org.xml.sax.Attributes;
+
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
-import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
-import org.xml.sax.Attributes;
+import de.danoeh.antennapod.core.util.DateUtils;
/**
* SAX-Parser for reading RSS-Feeds
@@ -129,7 +131,7 @@ public class NSRSS20 extends Namespace {
}
} else if (top.equals(PUBDATE) && second.equals(ITEM)) {
state.getCurrentItem().setPubDate(
- SyndDateUtils.parseRFC822Date(content));
+ DateUtils.parse(content));
} else if (top.equals(URL) && second.equals(IMAGE) && third != null
&& third.equals(CHANNEL)) {
state.getFeed().getImage().setDownload_url(content);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
index a2e5d0187..64b82100e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
@@ -10,7 +10,7 @@ import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
-import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
+import de.danoeh.antennapod.core.util.DateUtils;
public class NSSimpleChapters extends Namespace {
private static final String TAG = "NSSimpleChapters";
@@ -33,7 +33,7 @@ public class NSSimpleChapters extends Namespace {
try {
state.getCurrentItem()
.getChapters()
- .add(new SimpleChapter(SyndDateUtils
+ .add(new SimpleChapter(DateUtils
.parseTimeString(attributes.getValue(START)),
attributes.getValue(TITLE), state.getCurrentItem(),
attributes.getValue(HREF)));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
index 3928c65b3..abff5b2db 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
@@ -13,8 +13,8 @@ import de.danoeh.antennapod.core.syndication.namespace.NSITunes;
import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
-import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
+import de.danoeh.antennapod.core.util.DateUtils;
public class NSAtom extends Namespace {
private static final String TAG = "NSAtom";
@@ -191,12 +191,12 @@ public class NSAtom extends Namespace {
if (second.equals(ENTRY)
&& state.getCurrentItem().getPubDate() == null) {
state.getCurrentItem().setPubDate(
- SyndDateUtils.parseRFC3339Date(content));
+ DateUtils.parse(content));
}
} else if (top.equals(PUBLISHED)) {
if (second.equals(ENTRY)) {
state.getCurrentItem().setPubDate(
- SyndDateUtils.parseRFC3339Date(content));
+ DateUtils.parse(content));
}
} else if (top.equals(IMAGE)) {
state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java
deleted file mode 100644
index a9929d7b1..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/util/SyndDateUtils.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package de.danoeh.antennapod.core.syndication.util;
-
-import android.util.Log;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-import de.danoeh.antennapod.core.BuildConfig;
-
-/**
- * Parses several date formats.
- */
-public class SyndDateUtils {
- private static final String TAG = "DateUtils";
-
- private static final String[] RFC822DATES = {"dd MMM yy HH:mm:ss Z",
- "dd MMM yy HH:mm Z"};
-
- /**
- * RFC 3339 date format for UTC dates.
- */
- public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-
- /**
- * RFC 3339 date format for localtime dates with offset.
- */
- public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ";
-
- public static final String ISO8601_SHORT = "yyyy-MM-dd";
-
- private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat(RFC822DATES[0], Locale.US);
- }
-
- };
-
- private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat(RFC3339UTC, Locale.US);
- }
-
- };
-
- private static ThreadLocal<SimpleDateFormat> ISO8601ShortFormatter = new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat(ISO8601_SHORT, Locale.US);
- }
-
- };
-
- public static Date parseRFC822Date(String date) {
- Date result = null;
- if (date.contains("PDT")) {
- date = date.replace("PDT", "PST8PDT");
- }
- if (date.contains(",")) {
- // Remove day of the week
- date = date.substring(date.indexOf(",") + 1).trim();
- }
- SimpleDateFormat format = RFC822Formatter.get();
-
- for (String RFC822DATE : RFC822DATES) {
- try {
- format.applyPattern(RFC822DATE);
- result = format.parse(date);
- break;
- } catch (ParseException e) {
- if (BuildConfig.DEBUG) Log.d(TAG, "ParserException", e);
- }
- }
- if (result == null) {
- Log.e(TAG, "Unable to parse feed date correctly:" + date);
- }
-
- return result;
- }
-
- public static Date parseRFC3339Date(String date) {
- Date result = null;
- SimpleDateFormat format = RFC3339Formatter.get();
- boolean isLocal = date.endsWith("Z");
- if (date.contains(".")) {
- // remove secfrac
- int fracIndex = date.indexOf(".");
- String first = date.substring(0, fracIndex);
- String second = null;
- if (isLocal) {
- second = date.substring(date.length() - 1);
- } else {
- if (date.contains("+")) {
- second = date.substring(date.indexOf("+"));
- } else {
- second = date.substring(date.indexOf("-"));
- }
- }
-
- date = first + second;
- }
- if (isLocal) {
- try {
- result = format.parse(date);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- } else {
- format.applyPattern(RFC3339LOCAL);
- // remove last colon
- StringBuffer buf = new StringBuffer(date.length() - 1);
- int colonIdx = date.lastIndexOf(':');
- for (int x = 0; x < date.length(); x++) {
- if (x != colonIdx)
- buf.append(date.charAt(x));
- }
- String bufStr = buf.toString();
- try {
- result = format.parse(bufStr);
- } catch (ParseException e) {
- e.printStackTrace();
- Log.e(TAG, "Unable to parse date");
- } finally {
- format.applyPattern(RFC3339UTC);
- }
-
- }
-
- return result;
-
- }
-
- public static Date parseISO8601Date(String date) {
- if(date.length() > ISO8601_SHORT.length()) {
- return parseRFC3339Date(date);
- }
- Date result = null;
- if(date.length() == "YYYYMMDD".length()) {
- date = date.substring(0, 4) + "-" + date.substring(4, 6) + "-" + date.substring(6,8);
- }
- SimpleDateFormat format = ISO8601ShortFormatter.get();
- try {
- result = format.parse(date);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- return result;
- }
-
- /**
- * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to
- * milliseconds.
- *
- * @throws java.lang.NumberFormatException if the number segments contain invalid numbers.
- */
- public static long parseTimeString(final String time) {
- String[] parts = time.split(":");
- long result = 0;
- int idx = 0;
- if (parts.length == 3) {
- // string has hours
- result += Integer.valueOf(parts[idx]) * 3600000L;
- idx++;
- }
- if (parts.length >= 2) {
- result += Integer.valueOf(parts[idx]) * 60000L;
- idx++;
- result += (Float.valueOf(parts[idx])) * 1000L;
- }
- return result;
- }
-
- public static String formatRFC822Date(Date date) {
- SimpleDateFormat format = RFC822Formatter.get();
- return format.format(date);
- }
-
- public static String formatRFC3339Local(Date date) {
- SimpleDateFormat format = RFC3339Formatter.get();
- format.applyPattern(RFC3339LOCAL);
- String result = format.format(date);
- format.applyPattern(RFC3339UTC);
- return result;
- }
-
- public static String formatRFC3339UTC(Date date) {
- SimpleDateFormat format = RFC3339Formatter.get();
- format.applyPattern(RFC3339UTC);
- return format.format(date);
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
new file mode 100644
index 000000000..b6df2dc85
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
@@ -0,0 +1,114 @@
+package de.danoeh.antennapod.core.util;
+
+import android.util.Log;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Parses several date formats.
+ */
+public class DateUtils {
+
+ private static final String TAG = "DateUtils";
+
+ public static Date parse(final String input) {
+ if(input == null) {
+ throw new IllegalArgumentException("Date most not be null");
+ }
+ String date = input.replace('/', '-');
+ if(date.contains(".")) {
+ int start = date.indexOf('.');
+ int current = start+1;
+ while(current < date.length() && Character.isDigit(date.charAt(current))) {
+ current++;
+ }
+ if(current - start > 4) {
+ if(current < date.length()-1) {
+ date = date.substring(0, start + 4) + date.substring(current);
+ } else {
+ date = date.substring(0, start + 4);
+ }
+ } else if(current - start < 4) {
+ if(current < date.length()-1) {
+ date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start)) + date.substring(current);
+ } else {
+ date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start));
+ }
+
+ }
+ }
+ String[] patterns = {
+ "dd MMM yy HH:mm:ss Z",
+ "dd MMM yy HH:mm Z",
+ "EEE, dd MMM yyyy HH:mm:ss Z",
+ "EEE, dd MMMM yyyy HH:mm:ss Z",
+ "EEEE, dd MMM yy HH:mm:ss Z",
+ "EEE MMM d HH:mm:ss yyyy",
+ "yyyy-MM-dd'T'HH:mm:ss",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS Z",
+ "yyyy-MM-dd'T'HH:mm:ssZ",
+ "yyyy-MM-dd'T'HH:mm:ss'Z'",
+ "yyyy-MM-ddZ",
+ "yyyy-MM-dd"
+ };
+ SimpleDateFormat parser = new SimpleDateFormat("", Locale.US);
+ parser.setLenient(false);
+ ParsePosition pos = new ParsePosition(0);
+ for(String pattern : patterns) {
+ parser.applyPattern(pattern);
+ pos.setIndex(0);
+ Date result = parser.parse(date, pos);
+ if(result != null && pos.getIndex() == date.length()) {
+ return result;
+ }
+ }
+
+ Log.d(TAG, "Could not parse date string \"" + input + "\"");
+ return null;
+ }
+
+
+ /**
+ * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to
+ * milliseconds.
+ *
+ * @throws java.lang.NumberFormatException if the number segments contain invalid numbers.
+ */
+ public static long parseTimeString(final String time) {
+ String[] parts = time.split(":");
+ long result = 0;
+ int idx = 0;
+ if (parts.length == 3) {
+ // string has hours
+ result += Integer.valueOf(parts[idx]) * 3600000L;
+ idx++;
+ }
+ if (parts.length >= 2) {
+ result += Integer.valueOf(parts[idx]) * 60000L;
+ idx++;
+ result += (Float.valueOf(parts[idx])) * 1000L;
+ }
+ return result;
+ }
+
+ public static String formatRFC822Date(Date date) {
+ SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
+ return format.format(date);
+ }
+
+ public static String formatRFC3339Local(Date date) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
+ return format.format(date);
+ }
+
+ public static String formatRFC3339UTC(Date date) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ return format.format(date);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
index 4c23b161b..029e7fe84 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
@@ -1,11 +1,12 @@
package de.danoeh.antennapod.core.util;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
import java.util.ArrayList;
import java.util.List;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
public class EpisodeFilter {
+
private EpisodeFilter() {
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
new file mode 100644
index 000000000..673c81235
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
@@ -0,0 +1,240 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.Arrays;
+
+/**
+ * Fast and memory efficient int list
+ */
+public final class IntList {
+
+ private int[] values;
+ protected int size;
+
+ /**
+ * Constructs an empty instance with a default initial capacity.
+ */
+ public IntList() {
+ this(4);
+ }
+
+ /**
+ * Constructs an empty instance.
+ *
+ * @param initialCapacity {@code >= 0;} initial capacity of the list
+ */
+ public IntList(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ values = new int[initialCapacity];
+ size = 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ int value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof IntList)) {
+ return false;
+ }
+ IntList otherList = (IntList) other;
+ if (size != otherList.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (values[i] != otherList.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer(size * 5 + 10);
+ sb.append("IntList{");
+ for (int i = 0; i < size; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(values[i]);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Gets the number of elements in this list.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Gets the indicated value.
+ *
+ * @param n {@code >= 0, < size();} which element
+ * @return the indicated element's value
+ */
+ public int get(int n) {
+ if (n >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[n];
+ }
+
+ /**
+ * Sets the value at the given index.
+ *
+ * @param index the index at which to put the specified object.
+ * @param value the object to add.
+ * @return the previous element at the index.
+ */
+ public int set(int index, int value) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ int result = values[index];
+ values[index] = value;
+ return result;
+ }
+
+ /**
+ * Adds an element to the end of the list. This will increase the
+ * list's capacity if necessary.
+ *
+ * @param value the value to add
+ */
+ public void add(int value) {
+ growIfNeeded();
+ values[size++] = value;
+ }
+
+ /**
+ * Inserts element into specified index, moving elements at and above
+ * that index up one. May not be used to insert at an index beyond the
+ * current size (that is, insertion as a last element is legal but
+ * no further).
+ *
+ * @param n {@code >= 0, <=size();} index of where to insert
+ * @param value value to insert
+ */
+ public void insert(int n, int value) {
+ if (n > size) {
+ throw new IndexOutOfBoundsException("n > size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+
+ growIfNeeded();
+
+ System.arraycopy (values, n, values, n+1, size - n);
+ values[n] = value;
+ size++;
+ }
+
+ /**
+ * Removes value from this list.
+ *
+ * @param value value to remove
+ * return {@code true} if the value was removed, {@code false} otherwise
+ */
+ public boolean remove(int value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ size--;
+ System.arraycopy(values, i+1, values, i, size-i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes an element at a given index, shifting elements at greater
+ * indicies down one.
+ *
+ * @param index index of element to remove
+ */
+ public void removeIndex(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ size--;
+ System.arraycopy (values, index + 1, values, index, size - index);
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == values.length) {
+ // Resize.
+ int[] newArray = new int[size * 3 / 2 + 10];
+ System.arraycopy(values, 0, newArray, 0, size);
+ values = newArray;
+ }
+ }
+
+ /**
+ * Returns the index of the given value, or -1 if the value does not
+ * appear in the list.
+ *
+ * @param value value to find
+ * @return index of value or -1
+ */
+ public int indexOf(int value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all values from this list.
+ */
+ public void clear() {
+ values = new int[4];
+ size = 0;
+ }
+
+
+ /**
+ * Returns true if the given value is contained in the list
+ *
+ * @param value value to look for
+ * @return {@code true} if this list contains {@code value}, {@code false} otherwise
+ */
+ public boolean contains(int value) {
+ return indexOf(value) >= 0;
+ }
+
+ /**
+ * Returns an array with a copy of this list's values
+ *
+ * @return array with a copy of this list's values
+ */
+ public int[] toArray() {
+ return Arrays.copyOf(values, size);
+
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
new file mode 100644
index 000000000..33fd252eb
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
@@ -0,0 +1,252 @@
+package de.danoeh.antennapod.core.util;
+
+
+/**
+ * Fast and memory efficient long to long map
+ */
+public class LongIntMap {
+
+ private long[] keys;
+ private int[] values;
+ private int size;
+
+ /**
+ * Creates a new LongLongMap containing no mappings.
+ */
+ public LongIntMap() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseLongArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public LongIntMap(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ keys = new long[initialCapacity];
+ values = new int[initialCapacity];
+ size = 0;
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == keys.length) {
+ // Resize.
+ long[] newKeysArray = new long[size * 3 / 2 + 10];
+ int[] newValuesArray = new int[size * 3 / 2 + 10];
+ System.arraycopy(keys, 0, newKeysArray, 0, size);
+ System.arraycopy(values, 0, newValuesArray, 0, size);
+ keys = newKeysArray;
+ values = newValuesArray;
+ }
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public int get(long key) {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public int get(long key, int valueIfKeyNotFound) {
+ int index = indexOfKey(key);
+ if(index >= 0) {
+ return values[index];
+ } else {
+ return valueIfKeyNotFound;
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public boolean delete(long key) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ removeAt(index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(keys, index + 1, keys, index, size - (index + 1));
+ System.arraycopy(values, index + 1, values, index, size - (index + 1));
+ size--;
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, int value) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ values[index] = value;
+ } else {
+ growIfNeeded();
+ keys[size] = key;
+ values[size] = value;
+ size++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The keys corresponding to indices in ascending order are guaranteed to
+ * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+ * smallest key and <code>keyAt(size()-1)</code> will return the largest
+ * key.</p>
+ */
+ public long keyAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return keys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The values corresponding to indices in ascending order are guaranteed
+ * to be associated with keys in ascending order, e.g.,
+ * <code>valueAt(0)</code> will return the value associated with the
+ * smallest key and <code>valueAt(size()-1)</code> will return the value
+ * associated with the largest key.</p>
+ */
+ public int valueAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[index];
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ for(int i=0; i < size; i++) {
+ if(keys[i] == key) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(long value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ keys = new long[10];
+ values = new int[10];
+ size = 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof LongIntMap)) {
+ return false;
+ }
+ LongIntMap otherMap = (LongIntMap) other;
+ if (size != otherMap.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (keys[i] != otherMap.keys[i] ||
+ values[i] != otherMap.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ long value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "LongLongMap{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size * 28);
+ buffer.append("LongLongMap{");
+ for (int i=0; i < size; i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ long key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ long value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
new file mode 100644
index 000000000..8934f3272
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -0,0 +1,240 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.Arrays;
+
+/**
+ * Fast and memory efficient long list
+ */
+public final class LongList {
+
+ private long[] values;
+ private int size;
+
+ /**
+ * Constructs an empty instance with a default initial capacity.
+ */
+ public LongList() {
+ this(4);
+ }
+
+ /**
+ * Constructs an empty instance.
+ *
+ * @param initialCapacity {@code >= 0;} initial capacity of the list
+ */
+ public LongList(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ values = new long[initialCapacity];
+ size = 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ long value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof LongList)) {
+ return false;
+ }
+ LongList otherList = (LongList) other;
+ if (size != otherList.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (values[i] != otherList.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer(size * 5 + 10);
+ sb.append("LongList{");
+ for (int i = 0; i < size; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(values[i]);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Gets the number of elements in this list.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Gets the indicated value.
+ *
+ * @param n {@code >= 0, < size();} which element
+ * @return the indicated element's value
+ */
+ public long get(int n) {
+ if (n >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[n];
+ }
+
+ /**
+ * Sets the value at the given index.
+ *
+ * @param index the index at which to put the specified object.
+ * @param value the object to add.
+ * @return the previous element at the index.
+ */
+ public long set(int index, long value) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ long result = values[index];
+ values[index] = value;
+ return result;
+ }
+
+ /**
+ * Adds an element to the end of the list. This will increase the
+ * list's capacity if necessary.
+ *
+ * @param value the value to add
+ */
+ public void add(long value) {
+ growIfNeeded();
+ values[size++] = value;
+ }
+
+ /**
+ * Inserts element into specified index, moving elements at and above
+ * that index up one. May not be used to insert at an index beyond the
+ * current size (that is, insertion as a last element is legal but
+ * no further).
+ *
+ * @param n {@code >= 0, <=size();} index of where to insert
+ * @param value value to insert
+ */
+ public void insert(int n, int value) {
+ if (n > size) {
+ throw new IndexOutOfBoundsException("n > size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+
+ growIfNeeded();
+
+ System.arraycopy (values, n, values, n+1, size - n);
+ values[n] = value;
+ size++;
+ }
+
+ /**
+ * Removes value from this list.
+ *
+ * @param value value to remove
+ * return {@code true} if the value was removed, {@code false} otherwise
+ */
+ public boolean remove(long value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ size--;
+ System.arraycopy(values, i+1, values, i, size-i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes an element at a given index, shifting elements at greater
+ * indicies down one.
+ *
+ * @param index index of element to remove
+ */
+ public void removeIndex(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ size--;
+ System.arraycopy (values, index + 1, values, index, size - index);
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == values.length) {
+ // Resize.
+ long[] newArray = new long[size * 3 / 2 + 10];
+ System.arraycopy(values, 0, newArray, 0, size);
+ values = newArray;
+ }
+ }
+
+ /**
+ * Returns the index of the given value, or -1 if the value does not
+ * appear in the list.
+ *
+ * @param value value to find
+ * @return index of value or -1
+ */
+ public int indexOf(long value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all values from this list.
+ */
+ public void clear() {
+ values = new long[4];
+ size = 0;
+ }
+
+
+ /**
+ * Returns true if the given value is contained in the list
+ *
+ * @param value value to look for
+ * @return {@code true} if this list contains {@code value}, {@code false} otherwise
+ */
+ public boolean contains(long value) {
+ return indexOf(value) >= 0;
+ }
+
+ /**
+ * Returns an array with a copy of this list's values
+ *
+ * @return array with a copy of this list's values
+ */
+ public long[] toArray() {
+ return Arrays.copyOf(values, size);
+
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
index b321536a3..3a349e221 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
@@ -6,12 +6,13 @@ import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import java.util.Arrays;
import java.util.List;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
public class NetworkUtils {
private static final String TAG = "NetworkUtils";
@@ -66,4 +67,18 @@ public class NetworkUtils {
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnected();
}
+
+ public static boolean isDownloadAllowed(Context context) {
+ return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(context);
+ }
+
+ public static boolean connectedToWifi(Context context) {
+ ConnectivityManager connManager = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo mWifi = connManager
+ .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ return mWifi.isConnected();
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
index 8e40ae184..7377b202d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
@@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.util;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
import java.util.Iterator;
import java.util.List;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
/**
* Provides methods for accessing the queue. It is possible to load only a part of the information about the queue that
* is stored in the database (e.g. sometimes the user just has to test if a specific item is contained in the List.
@@ -25,23 +25,6 @@ public abstract class QueueAccess {
public abstract boolean remove(long id);
private QueueAccess() {
-
- }
-
- public static QueueAccess IDListAccess(final List<Long> ids) {
- return new QueueAccess() {
- @Override
- public boolean contains(long id) {
- return (ids != null) && ids.contains(id);
- }
-
- @Override
- public boolean remove(long id) {
- return ids.remove(id);
- }
-
-
- };
}
public static QueueAccess ItemListAccess(final List<FeedItem> items) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
index 90caad946..50792ae26 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
@@ -26,7 +26,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.TimeZone;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.asynctask.FlattrTokenFetcher;
@@ -65,18 +64,15 @@ public class FlattrUtils {
private static AccessToken retrieveToken() {
if (cachedToken == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Retrieving access token");
+ Log.d(TAG, "Retrieving access token");
String token = PreferenceManager.getDefaultSharedPreferences(
ClientConfig.applicationCallbacks.getApplicationInstance())
.getString(PREF_ACCESS_TOKEN, null);
if (token != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Found access token. Caching.");
+ Log.d(TAG, "Found access token. Caching.");
cachedToken = new AccessToken(token);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No access token found");
+ Log.d(TAG, "No access token found");
return null;
}
}
@@ -97,8 +93,7 @@ public class FlattrUtils {
}
public static void storeToken(AccessToken token) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Storing token");
+ Log.d(TAG, "Storing token");
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(ClientConfig.applicationCallbacks.getApplicationInstance()).edit();
if (token != null) {
@@ -111,8 +106,7 @@ public class FlattrUtils {
}
public static void deleteToken() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleting flattr token");
+ Log.d(TAG, "Deleting flattr token");
storeToken(null);
}
@@ -169,15 +163,11 @@ public class FlattrUtils {
}
}
- if (BuildConfig.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());
- }
+ 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");
}
@@ -191,8 +181,7 @@ public class FlattrUtils {
}
public static void revokeAccessToken(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Revoking access token");
+ Log.d(TAG, "Revoking access token");
deleteToken();
FlattrServiceCreator.deleteFlattrService();
showRevokeDialog(context);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
index 0e03bc8b4..26c712af3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
@@ -1,9 +1,6 @@
package de.danoeh.antennapod.core.util.gui;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.Parcelable;
-import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
@@ -16,23 +13,36 @@ import de.danoeh.antennapod.core.R;
import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
-public class UndoBarController {
+public class UndoBarController<T> {
private View mBarView;
private TextView mMessageView;
private ViewPropertyAnimator mBarAnimator;
private Handler mHideHandler = new Handler();
- private UndoListener mUndoListener;
+ private UndoListener<T> mUndoListener;
// State objects
- private Parcelable mUndoToken;
+ private T mUndoToken;
private CharSequence mUndoMessage;
- public interface UndoListener {
- void onUndo(Parcelable token);
+ public interface UndoListener<T> {
+ /**
+ * This callback function is called when the undo button is pressed
+ *
+ * @param token
+ */
+ void onUndo(T token);
+
+ /**
+ *
+ * This callback function is called when the bar fades out without button press
+ *
+ * @param token
+ */
+ void onHide(T token);
}
- public UndoBarController(View undoBarView, UndoListener undoListener) {
+ public UndoBarController(View undoBarView, UndoListener<T> undoListener) {
mBarView = undoBarView;
mBarAnimator = animate(mBarView);
mUndoListener = undoListener;
@@ -50,7 +60,7 @@ public class UndoBarController {
hideUndoBar(true);
}
- public void showUndoBar(boolean immediate, CharSequence message, Parcelable undoToken) {
+ public void showUndoBar(boolean immediate, CharSequence message, T undoToken) {
mUndoToken = undoToken;
mUndoMessage = message;
mMessageView.setText(mUndoMessage);
@@ -73,6 +83,15 @@ public class UndoBarController {
}
}
+ public boolean isShowing() {
+ return mBarView.getVisibility() == View.VISIBLE;
+ }
+
+ public void close() {
+ hideUndoBar(true);
+ mUndoListener.onHide(mUndoToken);
+ }
+
public void hideUndoBar(boolean immediate) {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
@@ -96,26 +115,11 @@ public class UndoBarController {
}
}
- public void onSaveInstanceState(Bundle outState) {
- outState.putCharSequence("undo_message", mUndoMessage);
- outState.putParcelable("undo_token", mUndoToken);
- }
-
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- mUndoMessage = savedInstanceState.getCharSequence("undo_message");
- mUndoToken = savedInstanceState.getParcelable("undo_token");
-
- if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) {
- showUndoBar(true, mUndoMessage, mUndoToken);
- }
- }
- }
-
private Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hideUndoBar(false);
+ mUndoListener.onHide(mUndoToken);
}
};
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index 26dd2ec4c..a0d12d3e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -1,7 +1,13 @@
package de.danoeh.antennapod.core.util.playback;
import android.app.Activity;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.media.MediaPlayer;
import android.os.AsyncTask;
@@ -19,13 +25,18 @@ import android.widget.TextView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
-import de.danoeh.antennapod.core.BuildConfig;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
@@ -33,8 +44,6 @@ import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils;
-import java.util.concurrent.*;
-
/**
* Communicates with the playback service. GUI classes should use this class to
* control playback instead of communicating with the PlaybackService directly.
@@ -118,8 +127,7 @@ public abstract class PlaybackController {
* example in the activity's onStop() method.
*/
public void release() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Releasing PlaybackController");
+ Log.d(TAG, "Releasing PlaybackController");
try {
activity.unregisterReceiver(statusUpdate);
@@ -164,8 +172,7 @@ public abstract class PlaybackController {
* as the arguments of the launch intent.
*/
private void bindToService() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Trying to connect to service");
+ Log.d(TAG, "Trying to connect to service");
AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void... voids) {
@@ -177,7 +184,7 @@ public abstract class PlaybackController {
boolean bound = false;
if (!PlaybackService.started) {
if (serviceIntent != null) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Calling start service");
+ Log.d(TAG, "Calling start service");
activity.startService(serviceIntent);
bound = activity.bindService(serviceIntent, mConnection, 0);
} else {
@@ -186,14 +193,11 @@ public abstract class PlaybackController {
handleStatus();
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "PlaybackService is running, trying to connect without start command.");
+ Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
bound = activity.bindService(new Intent(activity,
PlaybackService.class), mConnection, 0);
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Result for service binding: " + bound);
+ Log.d(TAG, "Result for service binding: " + bound);
}
};
intentLoader.execute();
@@ -204,8 +208,7 @@ public abstract class PlaybackController {
* played media or null if no last played media could be found.
*/
private Intent getPlayLastPlayedMediaIntent() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Trying to restore last played media");
+ Log.d(TAG, "Trying to restore last played media");
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(activity.getApplicationContext());
long currentlyPlayingMedia = PlaybackPreferences
@@ -233,8 +236,7 @@ public abstract class PlaybackController {
return serviceIntent;
}
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No last played media found");
+ Log.d(TAG, "No last played media found");
return null;
}
@@ -246,8 +248,7 @@ public abstract class PlaybackController {
|| (positionObserverFuture != null && positionObserverFuture
.isDone()) || positionObserverFuture == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting up position observer");
+ Log.d(TAG, "Setting up position observer");
positionObserver = new MediaPositionObserver();
positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
positionObserver, MediaPositionObserver.WAITING_INTERVALL,
@@ -259,8 +260,7 @@ public abstract class PlaybackController {
private void cancelPositionObserver() {
if (positionObserverFuture != null) {
boolean result = positionObserverFuture.cancel(true);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "PositionObserver cancelled. Result: " + result);
+ Log.d(TAG, "PositionObserver cancelled. Result: " + result);
}
}
@@ -272,8 +272,7 @@ public abstract class PlaybackController {
.getService();
if (!released) {
queryService();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Connection to Service established");
+ Log.d(TAG, "Connection to Service established");
} else {
Log.i(TAG, "Connection to playback service has been established, but controller has already been released");
}
@@ -282,17 +281,14 @@ public abstract class PlaybackController {
@Override
public void onServiceDisconnected(ComponentName name) {
playbackService = null;
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Disconnected from Service");
-
+ Log.d(TAG, "Disconnected from Service");
}
};
protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received statusUpdate Intent.");
+ Log.d(TAG, "Received statusUpdate Intent.");
if (isConnectedToPlaybackService()) {
PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo();
status = info.playerStatus;
@@ -349,8 +345,7 @@ public abstract class PlaybackController {
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Bad arguments. Won't handle intent");
+ Log.d(TAG, "Bad arguments. Won't handle intent");
}
} else {
bindToService();
@@ -421,6 +416,7 @@ public abstract class PlaybackController {
pauseResource = R.drawable.ic_av_pause_circle_outline_80dp;
}
+ Log.d(TAG, "status: " + status.toString());
switch (status) {
case ERROR:
@@ -466,6 +462,7 @@ public abstract class PlaybackController {
updatePlayButtonAppearance(playResource, playText);
break;
case SEEKING:
+ onPositionObserverUpdate();
postStatusMsg(R.string.player_seeking_msg);
break;
case INITIALIZED:
@@ -501,8 +498,7 @@ public abstract class PlaybackController {
* information has to be refreshed
*/
void queryService() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Querying service info");
+ Log.d(TAG, "Querying service info");
if (playbackService != null) {
status = playbackService.getStatus();
media = playbackService.getPlayable();
@@ -610,28 +606,6 @@ public abstract class PlaybackController {
};
}
- public OnClickListener newOnRevButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(-UserPreferences.getSeekDeltaMs());
- }
- }
- };
- }
-
- public OnClickListener newOnFFButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(UserPreferences.getSeekDeltaMs());
- }
- }
- };
- }
-
public boolean serviceAvailable() {
return playbackService != null;
}