summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorTom Hennen <TomHennen@users.noreply.github.com>2015-04-09 19:34:23 -0400
committerTom Hennen <TomHennen@users.noreply.github.com>2015-04-09 19:34:23 -0400
commitb44e0dde5810fdbeb17e5e433bc4512ec60fdc2f (patch)
tree1abbec62b1e3943631901ab221391310857ec3c3 /core/src
parent768f4b169b0ed2f1cc68455a25e661d5e984d88f (diff)
parent0b4b328324489e68ada274faeb751ac79d7cca96 (diff)
downloadAntennaPod-b44e0dde5810fdbeb17e5e433bc4512ec60fdc2f.zip
Merge pull request #706 from mfietz/feature/gpodder_episode_actions
Sync episode actions with gpodder, smart mark as played.
Diffstat (limited to 'core/src')
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java59
-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/FeedItem.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java6
-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.java102
-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.java83
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java220
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java187
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java82
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java68
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java118
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java15
-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.java140
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java241
-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/gui/UndoBarController.java55
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java36
-rw-r--r--core/src/main/res/values/arrays.xml9
-rw-r--r--core/src/main/res/values/strings.xml10
33 files changed, 1653 insertions, 576 deletions
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java
new file mode 100644
index 000000000..cca753895
--- /dev/null
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java
@@ -0,0 +1,59 @@
+package de.danoeh.antennapod.core.util;
+
+
+import android.test.AndroidTestCase;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+public class DateUtilsTest extends AndroidTestCase {
+
+ public void testParseDateWithMicroseconds() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ Date expected = new Date(exp.getTimeInMillis() + 963);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.963870");
+ assertEquals(expected, actual);
+ }
+
+ public void testParseDateWithCentiseconds() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ Date expected = new Date(exp.getTimeInMillis() + 960);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.96");
+ assertEquals(expected, actual);
+ }
+
+ public void testParseDateWithDeciseconds() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4);
+ Date expected = new Date(exp.getTimeInMillis() + 900);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.9");
+ assertEquals(expected.getTime()/1000, actual.getTime()/1000);
+ assertEquals(900, actual.getTime()%1000);
+ }
+
+ public void testParseDateWithMicrosecondsAndTimezone() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 6, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis() + 963);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.963870 +0700");
+ assertEquals(expected, actual);
+ }
+
+ public void testParseDateWithCentisecondsAndTimezone() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 6, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis() + 960);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.96 +0700");
+ assertEquals(expected, actual);
+ }
+
+ public void testParseDateWithDecisecondsAndTimezone() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 6, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis() + 900);
+ Date actual = DateUtils.parse("2015-03-28T13:31:04.9 +0700");
+ assertEquals(expected.getTime()/1000, actual.getTime()/1000);
+ assertEquals(900, actual.getTime()%1000);
+ }
+
+}
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/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index 5a4d869e7..4fd7a184c 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;
@@ -384,4 +387,9 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public boolean hasChapters() {
return hasChapters;
}
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
}
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..93f826894 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
@@ -12,6 +12,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;
@@ -146,6 +147,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;
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..9f1eec754
--- /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
+ }
+
+ 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 a353c984a..db242c3bc 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;
@@ -537,6 +540,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.
*
@@ -773,4 +855,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..0c431d60b
--- /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.getItemIdentifier(), 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..cfdd0c5d6 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() {
@@ -195,7 +219,23 @@ 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));
+ }
+ 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 +255,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 +277,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..022c03ca7 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
@@ -45,6 +45,7 @@ public class UserPreferences implements
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes";
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
+ public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
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";
@@ -79,6 +80,7 @@ public class UserPreferences implements
private boolean allowMobileUpdate;
private boolean displayOnlyEpisodes;
private boolean autoDelete;
+ private int smartMarkAsPlayedSecs;
private boolean autoFlattr;
private float autoFlattrPlayedDurationThreshold;
private int theme;
@@ -137,6 +139,7 @@ public class UserPreferences implements
allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false);
autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
+ smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
@@ -267,6 +270,11 @@ public class UserPreferences implements
return instance.autoDelete;
}
+ public static int getSmartMarkAsPlayedSecs() {
+ instanceAvailable();;
+ return instance.smartMarkAsPlayedSecs;
+ }
+
public static boolean isAutoFlattr() {
instanceAvailable();
return instance.autoFlattr;
@@ -372,8 +380,7 @@ public class UserPreferences implements
@Override
public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Registered change of user preferences. Key: " + key);
+ Log.d(TAG, "Registered change of user preferences. Key: " + key);
if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) {
downloadMediaOnWifiOnly = sp.getBoolean(
@@ -389,10 +396,10 @@ public class UserPreferences implements
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_SMART_MARK_AS_PLAYED_SECS)) {
+ smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
} else if (key.equals(PREF_AUTO_FLATTR)) {
autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
} else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) {
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 0f2a81dfb..e39197387 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.Collections;
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.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;
@@ -50,7 +57,7 @@ public class GpodnetSyncService extends Service {
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));
+ 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");
@@ -61,9 +68,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,64 +85,94 @@ 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;
}
+ syncSubscriptionChanges();
+ syncEpisodeActions();
stopSelf();
}
+ private synchronized void syncSubscriptionChanges() {
+ final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
+ try {
+ final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
+ GpodnetService service = tryLogin();
+
+ // first sync: download all subscriptions...
+ GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
+ GpodnetPreferences.getDeviceID(), timestamp);
+ long lastUpdate = subscriptionChanges.getTimestamp();
+
+ Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
+ processSubscriptionChanges(localSubscriptions, subscriptionChanges);
+
+ Collection<String> added;
+ Collection<String> removed;
+ if (timestamp == 0) {
+ added = localSubscriptions;
+ GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy());
+ removed = Collections.emptyList();
+ } else {
+ added = GpodnetPreferences.getAddedFeedsCopy();
+ removed = GpodnetPreferences.getRemovedFeedsCopy();
+ }
+ if(added.size() > 0 || removed.size() > 0) {
+ Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
+ added, removed));
+ GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
+ GpodnetPreferences.getDeviceID(), added, removed);
+ lastUpdate = uploadResponse.timestamp;
+ Log.d(TAG, "Upload changes response: " + uploadResponse);
+ GpodnetPreferences.removeAddedFeeds(added);
+ GpodnetPreferences.removeRemovedFeeds(removed);
+ }
+ GpodnetPreferences.setLastSubscriptionSyncTimestamp(lastUpdate);
+ clearErrorNotifications();
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ updateErrorNotification(e);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+
+ 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 processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
for (String downloadUrl : changes.getAdded()) {
if (!localSubscriptions.contains(downloadUrl)) {
@@ -149,6 +185,64 @@ public class GpodnetSyncService extends Service {
}
}
+ 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>();
+ Map<Pair<String, String>, GpodnetEpisodeAction> remoteMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
+ // make sure more recent local actions are not overwritten by older remote actions
+ 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);
+ }
+ }
+ 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 = remoteMostRecentPlayAction.get(key);
+ if (mostRecent == null) {
+ remoteMostRecentPlayAction.put(key, action);
+ } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ remoteMostRecentPlayAction.put(key, action);
+ }
+ }
+ break;
+ case DELETE:
+ // NEVER EVER call DBWriter.deleteFeedMediaOfItem() here, leads to an infinite loop
+ break;
+ }
+ }
+ for (GpodnetEpisodeAction action : remoteMostRecentPlayAction.values()) {
+ FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
+ if (playItem != null) {
+ playItem.getMedia().setPosition(action.getPosition() * 1000);
+ if(playItem.getMedia().hasAlmostEnded()) {
+ DBWriter.markItemRead(this, playItem, true, true);
+ DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
+ }
+ }
+ }
+ }
+
private void clearErrorNotifications() {
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(R.id.notification_gpodnet_sync_error);
@@ -156,7 +250,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 +280,7 @@ public class GpodnetSyncService extends Service {
private WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
@Override
public void onWaitCompleted() {
- syncChanges();
+ sync();
}
};
@@ -209,7 +303,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() {
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..d5f17c099 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;
@@ -800,6 +803,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());
}
@@ -1166,6 +1181,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/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 6f3eedcb2..43c345fec 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;
@@ -259,8 +260,7 @@ public class PlaybackService extends Service {
@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 +268,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 +277,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 +302,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) {
@@ -376,8 +372,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 +440,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 +463,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:
@@ -537,11 +546,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 +559,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 +614,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,12 +622,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);
@@ -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,16 @@ 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 (isAutoFlattrable(media) &&
+ (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(m.getPlayedDuration())
+ 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 +1027,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 +1070,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 +1092,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 +1104,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 +1150,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 +1160,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 +1170,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 +1230,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 +1288,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..f0acc3531 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;
@@ -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();
}
@@ -567,8 +579,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 +662,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 +726,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;
@@ -788,17 +798,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.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");
+ 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 +816,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 +878,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();
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..cde03adea 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,21 @@ 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 +76,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();
@@ -312,7 +314,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..de6c02de7 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()
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..a7c98c7c6 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,7 +2,6 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.database.Cursor;
-import android.database.SQLException;
import android.util.Log;
import java.util.ArrayList;
@@ -22,6 +21,7 @@ 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.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;
@@ -339,8 +339,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,21 +358,21 @@ 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));
@@ -384,6 +383,22 @@ public final class DBReader {
/**
+ * 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 +407,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();
@@ -680,6 +694,42 @@ public final class DBReader {
}
+ 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);
+ }
+ }
+ }
+ return item;
+ }
+
+ /**
+ * 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;
+ }
+
/**
* Loads additional information about a FeedItem, e.g. shownotes
*
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 e0e370b0d..9fa17bf72 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
@@ -34,7 +34,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;
@@ -524,8 +524,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,
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..bd0cfee5b 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;
@@ -32,15 +33,18 @@ 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 +124,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,8 +349,7 @@ 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) {
@@ -358,8 +369,7 @@ public class DBWriter {
}
if (queueModified) {
adapter.setQueue(queue);
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
}
if (unreadItemsModified && item != null) {
adapter.setSingleFeedItem(item);
@@ -427,8 +437,7 @@ public class DBWriter {
}
if (queueModified) {
adapter.setQueue(queue);
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
}
if (unreadItemsModified) {
adapter.setFeedItemlist(itemsToSave);
@@ -459,7 +468,7 @@ public class DBWriter {
adapter.clearQueue();
adapter.close();
- EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.CLEARED));
}
});
}
@@ -468,34 +477,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 +523,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 +547,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 +608,8 @@ public class DBWriter {
adapter.setQueue(queue);
if (broadcastUpdate) {
- EventDistributor.getInstance()
- .sendQueueUpdateBroadcast();
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item));
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, to));
}
}
@@ -628,6 +622,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 +646,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) {
@@ -1036,8 +1031,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");
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..f518a4f5f 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
@@ -1120,7 +1120,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 +1137,15 @@ 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 int getQueueSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
Cursor c = db.rawQuery(query, null);
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..6622eab73
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
@@ -0,0 +1,140 @@
+package de.danoeh.antennapod.core.util;
+
+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";
+
+ 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("dd MMM yy HH:mm:ss Z", Locale.US);
+ }
+
+ };
+
+ private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ }
+
+ };
+
+ public static Date parse(String date) {
+ if(date == null) {
+ throw new IllegalArgumentException("Date most not be null");
+ }
+ date = date.replace('/', ' ');
+ date = date.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",
+ "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;
+ }
+ }
+ 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/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
new file mode 100644
index 000000000..f5d0cab0c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -0,0 +1,241 @@
+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() {
+ Arrays.hashCode(values);
+ 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/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/gui/UndoBarController.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
index 0e03bc8b4..23d8cf7c7 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,14 @@ public class UndoBarController {
}
}
+ public boolean isShowing() {
+ return mBarView.getVisibility() == View.VISIBLE;
+ }
+
+ public void close() {
+ mHideHandler.post(mHideRunnable);
+ }
+
public void hideUndoBar(boolean immediate) {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
@@ -96,26 +114,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..17c752bb6 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,6 +25,13 @@ import android.widget.TextView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
+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.BuildConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Chapter;
@@ -33,8 +46,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 +129,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);
@@ -177,7 +187,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 +196,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();
@@ -272,8 +279,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,9 +288,7 @@ 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");
}
};
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 9b9079021..4bb29ac85 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <string-array name="smart_mark_as_played_values">
+ <item>0</item>
+ <item>15</item>
+ <item>30</item>
+ <item>45</item>
+ <item>60</item>
+ </string-array>
+
+
<string-array name="seek_delta_values">
<item>5</item>
<item>10</item>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 186651224..bdb3ad606 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -98,9 +98,9 @@
<string name="stream_label">Stream</string>
<string name="remove_label">Remove</string>
<string name="remove_episode_lable">Remove episode</string>
- <string name="mark_read_label">Mark as read</string>
- <string name="mark_unread_label">Mark as unread</string>
- <string name="marked_as_read_label">Marked as read</string>
+ <string name="mark_read_label">Mark as played</string>
+ <string name="mark_unread_label">Mark as unplayed</string>
+ <string name="marked_as_read_label">Marked as played</string>
<string name="add_to_queue_label">Add to Queue</string>
<string name="remove_from_queue_label">Remove from Queue</string>
<string name="visit_website_label">Visit Website</string>
@@ -220,6 +220,8 @@
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>
<string name="pref_auto_delete_sum">Delete episode when playback completes</string>
<string name="pref_auto_delete_title">Auto Delete</string>
+ <string name="pref_smart_mark_as_played_sum">Mark episodes as played even if less than a certain amount of seconds of playing time is still left</string>
+ <string name="pref_smart_mark_as_played_title">Smart mark as played</string>
<string name="playback_pref">Playback</string>
<string name="network_pref">Network</string>
<string name="pref_autoUpdateIntervall_title">Update interval</string>
@@ -277,6 +279,8 @@
<string name="pref_expand_notify_unsupport_toast">Android versions before 4.1 do not support expanded notifications.</string>
<string name="pref_queueAddToFront_sum">Add new episodes to the front of the queue.</string>
<string name="pref_queueAddToFront_title">Enqueue at front.</string>
+ <string name="pref_smart_mark_as_played_disabled">Disabled</string>
+
<!-- Auto-Flattr dialog -->
<string name="auto_flattr_enable">Enable automatic flattring</string>