summaryrefslogtreecommitdiff
path: root/core/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/assets/html-export-template.html85
-rw-r--r--core/src/main/assets/shownotes-style.css37
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/ExportWriter.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java30
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlWriter.java72
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java101
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/VolumeAdaptionSetting.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java45
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/BasicAuthorizationInterceptor.java67
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java61
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java36
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java132
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java145
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java93
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java63
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java226
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java51
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntList.java240
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/TimeSpeedConverter.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java47
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/CompareCompat.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/InReverseChronologicalOrder.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java126
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java2
-rw-r--r--core/src/main/res/drawable/borderless_button_dark.xml18
-rw-r--r--core/src/main/res/drawable/ic_volume_adaption_grey.xml9
-rw-r--r--core/src/main/res/drawable/ic_volume_adaption_white.xml9
-rw-r--r--core/src/main/res/values-v16/colors.xml4
-rw-r--r--core/src/main/res/values-v16/styles.xml9
-rw-r--r--core/src/main/res/values-v26/styles.xml6
-rw-r--r--core/src/main/res/values/arrays.xml12
-rw-r--r--core/src/main/res/values/attrs.xml2
-rw-r--r--core/src/main/res/values/strings.xml83
-rw-r--r--core/src/main/res/values/styles.xml9
56 files changed, 979 insertions, 1304 deletions
diff --git a/core/src/main/assets/html-export-template.html b/core/src/main/assets/html-export-template.html
new file mode 100644
index 000000000..ddab27a43
--- /dev/null
+++ b/core/src/main/assets/html-export-template.html
@@ -0,0 +1,85 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no' ?>
+<html>
+ <head>
+ <title>AntennaPod Subscriptions</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ * {
+ font-family: 'Lato', sans-serif;
+ font-weight: 300;
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+ html {
+ background: #3498db;
+ text-align: center;
+ padding: 10px;
+ }
+ h1 {
+ color: #fff;
+ font-weight: 300;
+ display: inline-block;
+ margin-top: 40px;
+ margin-bottom: 20px;
+ vertical-align: top;
+ }
+ ul {
+ text-align: center;
+ }
+ li {
+ width: 100%;
+ max-width: 500px;
+ display: block;
+ display: inline-flex;
+ padding: 10px;
+ }
+ li > div {
+ background: #fefefe;
+ padding: 10px;
+ display: inline-block;
+ width: 100%;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
+ text-align: left;
+ }
+ li span {
+ margin-top: 10px;
+ display: block;
+ }
+ a {
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ span a {
+ color: #3498db;
+ }
+ img {
+ width: 100px;
+ height: 100px;
+ margin-right: 10px;
+ }
+ li > div > img {
+ float: left;
+ }
+ li > div > p {
+ width: 100%;
+ }
+ body > a {
+ color: #ffffff;
+ display: inline-block;
+ margin-top: 10px;
+ clear:left;
+ }
+ </style>
+ </head>
+ <body>
+ <img src="https://antennapod.org/assets/img/antennapod-logo.png" />
+ <h1>AntennaPod Subscriptions</h1>
+ <ul>
+ {FEEDS}
+ </ul>
+ <a href="https://play.google.com/store/apps/details?id=de.danoeh.antennapod" target="_blank">Get AntennaPod</a>
+ </body>
+</html>
diff --git a/core/src/main/assets/shownotes-style.css b/core/src/main/assets/shownotes-style.css
new file mode 100644
index 000000000..d0c8564aa
--- /dev/null
+++ b/core/src/main/assets/shownotes-style.css
@@ -0,0 +1,37 @@
+@font-face {
+ font-family: 'Roboto-Light';
+ src: url('file:///android_asset/Roboto-Light.ttf');
+}
+* {
+ color: %s;
+ font-family: roboto-Light;
+ font-size: 13pt;
+ overflow-wrap: break-word;
+}
+a {
+ font-style: normal;
+ text-decoration: none;
+ font-weight: normal;
+ color: #00A8DF;
+}
+a.timecode {
+ color: #669900;
+}
+img, iframe {
+ display: block;
+ margin: 10 auto;
+ max-width: 100%%;
+ height: auto;
+}
+body {
+ margin: %dpx %dpx %dpx %dpx;
+}
+p#apNoShownotes {
+ position: fixed;
+ top: 50%%;
+ left: 50%%;
+ transform: translate(-50%%, -50%%);
+ text-align: center;
+ -webkit-text-size-adjust: none;
+ font-size: 80%%;
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
index 80ce6cf56..4c11d0489 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
@@ -27,7 +27,6 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.export.opml.OpmlElement;
import de.danoeh.antennapod.core.export.opml.OpmlReader;
import de.danoeh.antennapod.core.export.opml.OpmlWriter;
@@ -45,18 +44,6 @@ public class OpmlBackupAgent extends BackupAgentHelper {
addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this));
}
- private static void LOGD(String tag, String msg) {
- if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
- Log.d(tag, msg);
- }
- }
-
- private static void LOGD(String tag, String msg, Throwable tr) {
- if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) {
- Log.d(tag, msg, tr);
- }
- }
-
/**
* Class for backing up and restoring the OPML file.
*/
@@ -93,12 +80,12 @@ public class OpmlBackupAgent extends BackupAgentHelper {
try {
// Write OPML
- new OpmlWriter().writeDocument(DBReader.getFeedList(), writer);
+ new OpmlWriter().writeDocument(DBReader.getFeedList(), writer, mContext);
// Compare checksum of new and old file to see if we need to perform a backup at all
if (digester != null) {
byte[] newChecksum = digester.digest();
- LOGD(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16));
+ Log.d(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16));
// Get the old checksum
if (oldState != null) {
@@ -108,10 +95,10 @@ public class OpmlBackupAgent extends BackupAgentHelper {
if (len != -1) {
byte[] oldChecksum = new byte[len];
inState.read(oldChecksum);
- LOGD(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16));
+ Log.d(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16));
if (Arrays.equals(oldChecksum, newChecksum)) {
- LOGD(TAG, "Checksums are the same; won't backup");
+ Log.d(TAG, "Checksums are the same; won't backup");
return;
}
}
@@ -120,7 +107,7 @@ public class OpmlBackupAgent extends BackupAgentHelper {
writeNewStateDescription(newState, newChecksum);
}
- LOGD(TAG, "Backing up OPML");
+ Log.d(TAG, "Backing up OPML");
byte[] bytes = byteStream.toByteArray();
data.writeEntityHeader(OPML_ENTITY_KEY, bytes.length);
data.writeEntityData(bytes, bytes.length);
@@ -138,10 +125,10 @@ public class OpmlBackupAgent extends BackupAgentHelper {
@Override
public void restoreEntity(BackupDataInputStream data) {
- LOGD(TAG, "Backup restore");
+ Log.d(TAG, "Backup restore");
if (!OPML_ENTITY_KEY.equals(data.getKey())) {
- LOGD(TAG, "Unknown entity key: " + data.getKey());
+ Log.d(TAG, "Unknown entity key: " + data.getKey());
return;
}
@@ -167,7 +154,7 @@ public class OpmlBackupAgent extends BackupAgentHelper {
try {
downloader.downloadFeed(mContext, feed);
} catch (DownloadRequestException e) {
- LOGD(TAG, "Error while restoring/downloading feed", e);
+ Log.d(TAG, "Error while restoring/downloading feed", e);
}
}
} catch (XmlPullParserException e) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java
new file mode 100644
index 000000000..0ac7e1316
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java
@@ -0,0 +1,19 @@
+package de.danoeh.antennapod.core.event.settings;
+
+public class SpeedPresetChangedEvent {
+ private final float speed;
+ private final long feedId;
+
+ public SpeedPresetChangedEvent(float speed, long feedId) {
+ this.speed = speed;
+ this.feedId = feedId;
+ }
+
+ public float getSpeed() {
+ return speed;
+ }
+
+ public long getFeedId() {
+ return feedId;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java
new file mode 100644
index 000000000..3ed84f6a8
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java
@@ -0,0 +1,21 @@
+package de.danoeh.antennapod.core.event.settings;
+
+import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting;
+
+public class VolumeAdaptionChangedEvent {
+ private final VolumeAdaptionSetting volumeAdaptionSetting;
+ private final long feedId;
+
+ public VolumeAdaptionChangedEvent(VolumeAdaptionSetting volumeAdaptionSetting, long feedId) {
+ this.volumeAdaptionSetting = volumeAdaptionSetting;
+ this.feedId = feedId;
+ }
+
+ public VolumeAdaptionSetting getVolumeAdaptionSetting() {
+ return volumeAdaptionSetting;
+ }
+
+ public long getFeedId() {
+ return feedId;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/ExportWriter.java b/core/src/main/java/de/danoeh/antennapod/core/export/ExportWriter.java
index d6a187b21..e0f0d4626 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/export/ExportWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/export/ExportWriter.java
@@ -11,8 +11,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package de.danoeh.antennapod.core.export;
+import android.content.Context;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
@@ -21,9 +23,9 @@ import de.danoeh.antennapod.core.feed.Feed;
public interface ExportWriter {
- void writeDocument(List<Feed> feeds, Writer writer)
- throws IllegalArgumentException, IllegalStateException, IOException;
+ void writeDocument(List<Feed> feeds, Writer writer, Context context)
+ throws IllegalArgumentException, IllegalStateException, IOException;
- String fileExtension();
+ String fileExtension();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java b/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java
deleted file mode 100644
index 1ca126469..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package de.danoeh.antennapod.core.export.html;
-
-import de.danoeh.antennapod.core.export.CommonSymbols;
-
-class HtmlSymbols extends CommonSymbols {
-
- static final String HTML = "html";
-
- static final String ORDERED_LIST = "ol";
- static final String LIST_ITEM = "li";
-
- static final String HEADING = "h1";
-
- static final String LINK = "a";
- static final String LINK_DESTINATION = "href";
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlWriter.java b/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlWriter.java
index c24b39812..93b66daed 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlWriter.java
@@ -1,77 +1,45 @@
package de.danoeh.antennapod.core.export.html;
-import android.text.TextUtils;
+import android.content.Context;
import android.util.Log;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlSerializer;
-
+import de.danoeh.antennapod.core.export.ExportWriter;
+import de.danoeh.antennapod.core.feed.Feed;
import java.io.IOException;
+import java.io.InputStream;
import java.io.Writer;
import java.util.List;
-
-import de.danoeh.antennapod.core.export.ExportWriter;
-import de.danoeh.antennapod.core.feed.Feed;
+import org.apache.commons.io.IOUtils;
/** Writes HTML documents. */
public class HtmlWriter implements ExportWriter {
-
private static final String TAG = "HtmlWriter";
- private static final String ENCODING = "UTF-8";
- private static final String HTML_TITLE = "AntennaPod Subscriptions";
/**
* Takes a list of feeds and a writer and writes those into an HTML
* document.
- *
- * @throws IOException
- * @throws IllegalStateException
- * @throws IllegalArgumentException
*/
@Override
- public void writeDocument(List<Feed> feeds, Writer writer)
+ public void writeDocument(List<Feed> feeds, Writer writer, Context context)
throws IllegalArgumentException, IllegalStateException, IOException {
Log.d(TAG, "Starting to write document");
- XmlSerializer xs = Xml.newSerializer();
- xs.setFeature(HtmlSymbols.XML_FEATURE_INDENT_OUTPUT, true);
- xs.setOutput(writer);
- xs.startDocument(ENCODING, false);
- xs.startTag(null, HtmlSymbols.HTML);
- xs.startTag(null, HtmlSymbols.HEAD);
- xs.startTag(null, HtmlSymbols.TITLE);
- xs.text(HTML_TITLE);
- xs.endTag(null, HtmlSymbols.TITLE);
- xs.endTag(null, HtmlSymbols.HEAD);
+ InputStream templateStream = context.getAssets().open("html-export-template.html");
+ String template = IOUtils.toString(templateStream, "UTF-8");
+ String[] templateParts = template.split("\\{FEEDS\\}");
- xs.startTag(null, HtmlSymbols.BODY);
- xs.startTag(null, HtmlSymbols.HEADING);
- xs.text(HTML_TITLE);
- xs.endTag(null, HtmlSymbols.HEADING);
- xs.startTag(null, HtmlSymbols.ORDERED_LIST);
+ writer.append(templateParts[0]);
for (Feed feed : feeds) {
- xs.startTag(null, HtmlSymbols.LIST_ITEM);
- xs.text(feed.getTitle());
- if (!TextUtils.isEmpty(feed.getLink())) {
- xs.text(" [");
- xs.startTag(null, HtmlSymbols.LINK);
- xs.attribute(null, HtmlSymbols.LINK_DESTINATION, feed.getLink());
- xs.text("Website");
- xs.endTag(null, HtmlSymbols.LINK);
- xs.text("]");
- }
- xs.text(" [");
- xs.startTag(null, HtmlSymbols.LINK);
- xs.attribute(null, HtmlSymbols.LINK_DESTINATION, feed.getDownload_url());
- xs.text("Feed");
- xs.endTag(null, HtmlSymbols.LINK);
- xs.text("]");
- xs.endTag(null, HtmlSymbols.LIST_ITEM);
+ writer.append("<li><div><img src=\"");
+ writer.append(feed.getImageUrl());
+ writer.append("\" /><p>");
+ writer.append(feed.getTitle());
+ writer.append(" <span><a href=\"");
+ writer.append(feed.getLink());
+ writer.append("\">Website</a> • <a href=\"");
+ writer.append(feed.getDownload_url());
+ writer.append("\">Feed</a></span></p></div></li>\n");
}
- xs.endTag(null, HtmlSymbols.ORDERED_LIST);
- xs.endTag(null, HtmlSymbols.BODY);
- xs.endTag(null, HtmlSymbols.HTML);
- xs.endDocument();
+ writer.append(templateParts[1]);
Log.d(TAG, "Finished writing document");
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java b/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java
index fd0922f72..c93d4e8e0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlWriter.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.export.opml;
+import android.content.Context;
import android.util.Log;
import android.util.Xml;
@@ -17,62 +18,58 @@ import de.danoeh.antennapod.core.util.DateUtils;
/** Writes OPML documents. */
public class OpmlWriter implements ExportWriter {
- private static final String TAG = "OpmlWriter";
- private static final String ENCODING = "UTF-8";
- private static final String OPML_VERSION = "2.0";
- private static final String OPML_TITLE = "AntennaPod Subscriptions";
+ private static final String TAG = "OpmlWriter";
+ private static final String ENCODING = "UTF-8";
+ private static final String OPML_VERSION = "2.0";
+ private static final String OPML_TITLE = "AntennaPod Subscriptions";
- /**
- * Takes a list of feeds and a writer and writes those into an OPML
- * document.
- *
- * @throws IOException
- * @throws IllegalStateException
- * @throws IllegalArgumentException
- */
- @Override
- public void writeDocument(List<Feed> feeds, Writer writer)
- throws IllegalArgumentException, IllegalStateException, IOException {
- Log.d(TAG, "Starting to write document");
- XmlSerializer xs = Xml.newSerializer();
- xs.setFeature(OpmlSymbols.XML_FEATURE_INDENT_OUTPUT, true);
- xs.setOutput(writer);
+ /**
+ * Takes a list of feeds and a writer and writes those into an OPML
+ * document.
+ */
+ @Override
+ public void writeDocument(List<Feed> feeds, Writer writer, Context context)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ Log.d(TAG, "Starting to write document");
+ XmlSerializer xs = Xml.newSerializer();
+ xs.setFeature(OpmlSymbols.XML_FEATURE_INDENT_OUTPUT, true);
+ xs.setOutput(writer);
- xs.startDocument(ENCODING, false);
- xs.startTag(null, OpmlSymbols.OPML);
- xs.attribute(null, OpmlSymbols.VERSION, OPML_VERSION);
+ xs.startDocument(ENCODING, false);
+ xs.startTag(null, OpmlSymbols.OPML);
+ xs.attribute(null, OpmlSymbols.VERSION, OPML_VERSION);
- xs.startTag(null, OpmlSymbols.HEAD);
- xs.startTag(null, OpmlSymbols.TITLE);
- xs.text(OPML_TITLE);
- xs.endTag(null, OpmlSymbols.TITLE);
- xs.startTag(null, OpmlSymbols.DATE_CREATED);
- xs.text(DateUtils.formatRFC822Date(new Date()));
- xs.endTag(null, OpmlSymbols.DATE_CREATED);
- xs.endTag(null, OpmlSymbols.HEAD);
+ xs.startTag(null, OpmlSymbols.HEAD);
+ xs.startTag(null, OpmlSymbols.TITLE);
+ xs.text(OPML_TITLE);
+ xs.endTag(null, OpmlSymbols.TITLE);
+ xs.startTag(null, OpmlSymbols.DATE_CREATED);
+ xs.text(DateUtils.formatRFC822Date(new Date()));
+ xs.endTag(null, OpmlSymbols.DATE_CREATED);
+ xs.endTag(null, OpmlSymbols.HEAD);
- xs.startTag(null, OpmlSymbols.BODY);
- for (Feed feed : feeds) {
- xs.startTag(null, OpmlSymbols.OUTLINE);
- xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle());
- xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle());
- if (feed.getType() != null) {
- xs.attribute(null, OpmlSymbols.TYPE, feed.getType());
- }
- xs.attribute(null, OpmlSymbols.XMLURL, feed.getDownload_url());
- if (feed.getLink() != null) {
- xs.attribute(null, OpmlSymbols.HTMLURL, feed.getLink());
- }
- xs.endTag(null, OpmlSymbols.OUTLINE);
- }
- xs.endTag(null, OpmlSymbols.BODY);
- xs.endTag(null, OpmlSymbols.OPML);
- xs.endDocument();
- Log.d(TAG, "Finished writing document");
- }
+ xs.startTag(null, OpmlSymbols.BODY);
+ for (Feed feed : feeds) {
+ xs.startTag(null, OpmlSymbols.OUTLINE);
+ xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle());
+ xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle());
+ if (feed.getType() != null) {
+ xs.attribute(null, OpmlSymbols.TYPE, feed.getType());
+ }
+ xs.attribute(null, OpmlSymbols.XMLURL, feed.getDownload_url());
+ if (feed.getLink() != null) {
+ xs.attribute(null, OpmlSymbols.HTMLURL, feed.getLink());
+ }
+ xs.endTag(null, OpmlSymbols.OUTLINE);
+ }
+ xs.endTag(null, OpmlSymbols.BODY);
+ xs.endTag(null, OpmlSymbols.OPML);
+ xs.endDocument();
+ Log.d(TAG, "Finished writing document");
+ }
- public String fileExtension() {
- return "opml";
- }
+ public String fileExtension() {
+ return "opml";
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index d078ec1d1..b1598f111 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -171,7 +171,7 @@ public class Feed extends FeedFile implements ImageResource {
*/
public Feed(String url, String lastUpdate, String title, String username, String password) {
this(url, lastUpdate, title);
- preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, username, password);
+ preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password);
}
public static Feed fromCursor(Cursor cursor) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
index 8fdf2034f..b24c52266 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
@@ -28,19 +28,23 @@ public class FeedPreferences {
NO
}
private AutoDeleteAction auto_delete_action;
+
+ private VolumeAdaptionSetting volumeAdaptionSetting;
+
private String username;
private String password;
private float feedPlaybackSpeed;
- public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, String username, String password) {
- this(feedID, autoDownload, true, auto_delete_action, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
+ public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
+ this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
}
- private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
+ private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
this.feedID = feedID;
this.autoDownload = autoDownload;
this.keepUpdated = keepUpdated;
this.auto_delete_action = auto_delete_action;
+ this.volumeAdaptionSetting = volumeAdaptionSetting;
this.username = username;
this.password = password;
this.filter = filter;
@@ -52,6 +56,7 @@ public class FeedPreferences {
int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
int indexAutoRefresh = cursor.getColumnIndex(PodDBAdapter.KEY_KEEP_UPDATED);
int indexAutoDeleteAction = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DELETE_ACTION);
+ int indexVolumeAdaption = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_VOLUME_ADAPTION);
int indexUsername = cursor.getColumnIndex(PodDBAdapter.KEY_USERNAME);
int indexPassword = cursor.getColumnIndex(PodDBAdapter.KEY_PASSWORD);
int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER);
@@ -63,12 +68,14 @@ public class FeedPreferences {
boolean autoRefresh = cursor.getInt(indexAutoRefresh) > 0;
int autoDeleteActionIndex = cursor.getInt(indexAutoDeleteAction);
AutoDeleteAction autoDeleteAction = AutoDeleteAction.values()[autoDeleteActionIndex];
+ int volumeAdaptionValue = cursor.getInt(indexVolumeAdaption);
+ VolumeAdaptionSetting volumeAdaptionSetting = VolumeAdaptionSetting.fromInteger(volumeAdaptionValue);
String username = cursor.getString(indexUsername);
String password = cursor.getString(indexPassword);
String includeFilter = cursor.getString(indexIncludeFilter);
String excludeFilter = cursor.getString(indexExcludeFilter);
float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed);
- return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, username, password, new FeedFilter(includeFilter, excludeFilter), feedPlaybackSpeed);
+ return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, volumeAdaptionSetting, username, password, new FeedFilter(includeFilter, excludeFilter), feedPlaybackSpeed);
}
/**
@@ -144,10 +151,18 @@ public class FeedPreferences {
return auto_delete_action;
}
+ public VolumeAdaptionSetting getVolumeAdaptionSetting() {
+ return volumeAdaptionSetting;
+ }
+
public void setAutoDeleteAction(AutoDeleteAction auto_delete_action) {
this.auto_delete_action = auto_delete_action;
}
+ public void setVolumeAdaptionSetting(VolumeAdaptionSetting volumeAdaptionSetting) {
+ this.volumeAdaptionSetting = volumeAdaptionSetting;
+ }
+
public boolean getCurrentAutoDelete() {
switch (auto_delete_action) {
case GLOBAL:
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java b/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java
deleted file mode 100644
index ea8eb7871..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package de.danoeh.antennapod.core.feed;
-
-public class SearchResult {
- private final FeedComponent component;
- /** Additional information (e.g. where it was found) */
- private String subtitle;
- /** Higher value means more importance */
- private final int value;
-
- public SearchResult(FeedComponent component, int value, String subtitle) {
- super();
- this.component = component;
- this.value = value;
- this.subtitle = subtitle;
- }
-
- public FeedComponent getComponent() {
- return component;
- }
-
- public String getSubtitle() {
- return subtitle;
- }
-
- public void setSubtitle(String subtitle) {
- this.subtitle = subtitle;
- }
-
- public int getValue() {
- return value;
- }
-
-
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeAdaptionSetting.java b/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeAdaptionSetting.java
new file mode 100644
index 000000000..bf4fc582a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/VolumeAdaptionSetting.java
@@ -0,0 +1,32 @@
+package de.danoeh.antennapod.core.feed;
+
+public enum VolumeAdaptionSetting {
+ OFF(0, 1.0f),
+ LIGHT_REDUCTION(1, 0.5f),
+ HEAVY_REDUCTION(2, 0.2f);
+
+ private final int value;
+ private float adaptionFactor;
+
+ VolumeAdaptionSetting(int value, float adaptionFactor) {
+ this.value = value;
+ this.adaptionFactor = adaptionFactor;
+ }
+
+ public static VolumeAdaptionSetting fromInteger(int value) {
+ for (VolumeAdaptionSetting setting : values()) {
+ if (setting.value == value) {
+ return setting;
+ }
+ }
+ throw new IllegalArgumentException("Cannot map value to VolumeAdaptionSetting: " + value);
+ }
+
+ public int toInteger() {
+ return value;
+ }
+
+ public float getAdaptionFactor() {
+ return adaptionFactor;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
index f02334af5..8a13944e1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
@@ -11,6 +11,7 @@ import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
+import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
@@ -49,7 +50,6 @@ class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
if (internalClient == null) {
OkHttpClient.Builder builder = AntennapodHttpClient.newBuilder();
builder.interceptors().add(new NetworkAllowanceInterceptor());
- builder.interceptors().add(new BasicAuthenticationInterceptor());
internalClient = builder.build();
}
}
@@ -123,48 +123,5 @@ class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
.build();
}
}
-
- }
-
- private static class BasicAuthenticationInterceptor implements Interceptor {
-
- @Override
- public Response intercept(Chain chain) throws IOException {
- Request request = chain.request();
- String url = request.url().toString();
- String authentication = DBReader.getImageAuthentication(url);
-
- if(TextUtils.isEmpty(authentication)) {
- Log.d(TAG, "no credentials for '" + url + "'");
- return chain.proceed(request);
- }
-
- // add authentication
- String[] auth = authentication.split(":");
- if (auth.length != 2) {
- Log.d(TAG, "Invalid credentials for '" + url + "'");
- return chain.proceed(request);
- }
-
- String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1");
- Request newRequest = request
- .newBuilder()
- .addHeader("Authorization", credentials)
- .build();
- Log.d(TAG, "Basic authentication with ISO-8859-1 encoding");
- Response response = chain.proceed(newRequest);
- if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
- credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8");
- newRequest = request
- .newBuilder()
- .addHeader("Authorization", credentials)
- .build();
- Log.d(TAG, "Basic authentication with UTF-8 encoding");
- return chain.proceed(newRequest);
- } else {
- return response;
- }
- }
}
-
} \ No newline at end of file
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 00cafe226..383697fa2 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
@@ -803,14 +803,6 @@ public class UserPreferences {
prefs.edit().putString(PREF_MEDIA_PLAYER, "sonic").apply();
}
- public static void enableExoplayer() {
- prefs.edit().putString(PREF_MEDIA_PLAYER, PREF_MEDIA_PLAYER_EXOPLAYER).apply();
- }
-
- public static void enableBuiltin() {
- prefs.edit().putString(PREF_MEDIA_PLAYER, "builtin").apply();
- }
-
public static boolean stereoToMono() {
return prefs.getBoolean(PREF_STEREO_TO_MONO, false);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/BasicAuthorizationInterceptor.java b/core/src/main/java/de/danoeh/antennapod/core/service/BasicAuthorizationInterceptor.java
new file mode 100644
index 000000000..394eb3943
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/BasicAuthorizationInterceptor.java
@@ -0,0 +1,67 @@
+package de.danoeh.antennapod.core.service;
+
+import android.text.TextUtils;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.util.URIUtil;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class BasicAuthorizationInterceptor implements Interceptor {
+ private static final String TAG = "BasicAuthInterceptor";
+
+ @Override
+ @NonNull
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+
+ Response response = chain.proceed(request);
+
+ if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) {
+ return response;
+ }
+
+ String userInfo;
+ if (request.tag() instanceof DownloadRequest) {
+ DownloadRequest downloadRequest = (DownloadRequest) request.tag();
+ userInfo = URIUtil.getURIFromRequestUrl(downloadRequest.getSource()).getUserInfo();
+ if (TextUtils.isEmpty(userInfo)) {
+ userInfo = downloadRequest.getUsername() + ":" + downloadRequest.getPassword();
+ }
+ } else {
+ userInfo = DBReader.getImageAuthentication(request.url().toString());
+ }
+
+ if (TextUtils.isEmpty(userInfo)) {
+ Log.d(TAG, "no credentials for '" + request.url() + "'");
+ return response;
+ }
+
+ String[] parts = userInfo.split(":");
+ if (parts.length != 2) {
+ Log.d(TAG, "Invalid credentials for '" + request.url() + "'");
+ return response;
+ }
+
+ Request.Builder newRequest = request.newBuilder();
+ Log.d(TAG, "Authorization failed, re-trying with ISO-8859-1 encoded credentials");
+ String credentials = HttpDownloader.encodeCredentials(parts[0], parts[1], "ISO-8859-1");
+ newRequest.header("Authorization", credentials);
+ response = chain.proceed(newRequest.build());
+
+ if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) {
+ return response;
+ }
+
+ Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoded credentials");
+ credentials = HttpDownloader.encodeCredentials(parts[0], parts[1], "UTF-8");
+ newRequest.header("Authorization", credentials);
+ return chain.proceed(newRequest.build());
+ }
+}
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 b254cfc59..e4d9ff361 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
@@ -38,6 +38,7 @@ 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;
+import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
/**
@@ -185,15 +186,14 @@ public class GpodnetSyncService extends SafeJobIntentService {
// local changes are always superior to remote changes!
// add subscription if (1) not already subscribed and (2) not just unsubscribed
for (String downloadUrl : changes.getAdded()) {
- if (!localSubscriptions.contains(downloadUrl) &&
- !localRemoved.contains(downloadUrl)) {
+ if (!URLChecker.containsUrl(localSubscriptions, downloadUrl) && !localRemoved.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, null);
DownloadRequester.getInstance().downloadFeed(this, feed);
}
}
// remove subscription if not just subscribed (again)
for (String downloadUrl : changes.getRemoved()) {
- if(!localAdded.contains(downloadUrl)) {
+ if (!localAdded.contains(downloadUrl)) {
DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
index 2d9de1894..e6f2176f7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
@@ -159,14 +159,10 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
if (status == PlayerStatus.PLAYING) {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp);
- if (Build.VERSION.SDK_INT >= 15) {
- views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
- }
+ views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
} else {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
- if (Build.VERSION.SDK_INT >= 15) {
- views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
- }
+ views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
}
views.setOnClickPendingIntent(R.id.butPlay, createMediaButtonIntent());
} else {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index 672cbe9e1..8f368c76e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
@@ -112,6 +113,7 @@ public class AntennapodHttpClient {
}
return response;
});
+ builder.interceptors().add(new BasicAuthorizationInterceptor());
// set cookie handler
CookieManager cm = new CookieManager();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
index 8abfa3da3..551a20bf2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
@@ -54,9 +55,7 @@ public class HttpDownloader extends Downloader {
return;
}
- OkHttpClient.Builder httpClientBuilder = AntennapodHttpClient.newBuilder();
- httpClientBuilder.interceptors().add(new BasicAuthorizationInterceptor(request));
- OkHttpClient httpClient = httpClientBuilder.build();
+ OkHttpClient httpClient = AntennapodHttpClient.getHttpClient();
RandomAccessFile out = null;
InputStream connection;
ResponseBody responseBody = null;
@@ -65,6 +64,7 @@ public class HttpDownloader extends Downloader {
final URI uri = URIUtil.getURIFromRequestUrl(request.getSource());
Request.Builder httpReq = new Request.Builder().url(uri.toURL())
.header("User-Agent", ClientConfig.USER_AGENT);
+ httpReq.tag(request);
if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
// set header explicitly so that okhttp doesn't do transparent gzip
Log.d(TAG, "addHeader(\"Accept-Encoding\", \"identity\")");
@@ -308,63 +308,4 @@ public class HttpDownloader extends Downloader {
throw new AssertionError(e);
}
}
-
- private static class BasicAuthorizationInterceptor implements Interceptor {
-
- private final DownloadRequest downloadRequest;
-
- public BasicAuthorizationInterceptor(DownloadRequest downloadRequest) {
- this.downloadRequest = downloadRequest;
- }
-
- @Override
- public Response intercept(Chain chain) throws IOException {
- Request request = chain.request();
- String userInfo = URIUtil.getURIFromRequestUrl(downloadRequest.getSource()).getUserInfo();
-
- Response response = chain.proceed(request);
-
- if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) {
- return response;
- }
-
- Request.Builder newRequest = request.newBuilder();
-
- Log.d(TAG, "Authorization failed, re-trying with ISO-8859-1 encoded credentials");
- if (userInfo != null) {
- String[] parts = userInfo.split(":");
- if (parts.length == 2) {
- String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
- newRequest.header("Authorization", credentials);
- }
- } else if (!TextUtils.isEmpty(downloadRequest.getUsername()) && downloadRequest.getPassword() != null) {
- String credentials = encodeCredentials(downloadRequest.getUsername(), downloadRequest.getPassword(),
- "ISO-8859-1");
- newRequest.header("Authorization", credentials);
- }
-
- response = chain.proceed(newRequest.build());
-
- if (response.code() != HttpURLConnection.HTTP_UNAUTHORIZED) {
- return response;
- }
-
- Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoded credentials");
- if (userInfo != null) {
- String[] parts = userInfo.split(":");
- if (parts.length == 2) {
- String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
- newRequest.header("Authorization", credentials);
- }
- } else if (!TextUtils.isEmpty(downloadRequest.getUsername()) && downloadRequest.getPassword() != null) {
- String credentials = encodeCredentials(downloadRequest.getUsername(), downloadRequest.getPassword(),
- "UTF-8");
- newRequest.header("Authorization", credentials);
- }
-
- return chain.proceed(newRequest.build());
- }
-
- }
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
index 10d5bfa15..5900f84fc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
@@ -4,6 +4,7 @@ import android.util.Log;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DownloadRequester;
@@ -37,7 +38,7 @@ public class FeedParserTask implements Callable<FeedHandlerResult> {
feed.setId(request.getFeedfileId());
feed.setDownloaded(true);
feed.setPreferences(new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL,
- request.getUsername(), request.getPassword()));
+ VolumeAdaptionSetting.OFF, request.getUsername(), request.getPassword()));
feed.setPageNr(request.getArguments().getInt(DownloadRequester.REQUEST_ARG_PAGE_NR, 0));
DownloadError reason = null;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
index c250cd17f..8f7e84561 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
@@ -16,8 +16,9 @@ import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
@@ -71,7 +72,7 @@ public class ExoPlayerWrapper implements IPlayer {
loadControl.setBackBuffer(UserPreferences.getRewindSecs() * 1000 + 500, true);
SimpleExoPlayer p = ExoPlayerFactory.newSimpleInstance(mContext, new DefaultRenderersFactory(mContext),
new DefaultTrackSelector(), loadControl.createDefaultLoadControl());
- p.setSeekParameters(SeekParameters.PREVIOUS_SYNC);
+ p.setSeekParameters(SeekParameters.EXACT);
p.addListener(new Player.EventListener() {
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
@@ -170,7 +171,7 @@ public class ExoPlayerWrapper implements IPlayer {
@Override
public void prepare() throws IllegalStateException {
- mExoPlayer.prepare(mediaSource);
+ mExoPlayer.prepare(mediaSource, false, true);
}
@Override
@@ -216,7 +217,9 @@ public class ExoPlayerWrapper implements IPlayer {
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
true);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(mContext, null, httpDataSourceFactory);
- ExtractorMediaSource.Factory f = new ExtractorMediaSource.Factory(dataSourceFactory);
+ DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
+ extractorsFactory.setConstantBitrateSeekingEnabled(true);
+ ProgressiveMediaSource.Factory f = new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory);
mediaSource = f.createMediaSource(Uri.parse(s));
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
index 1bf0cfb18..6f7401698 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java
@@ -12,11 +12,11 @@ import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
-import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import org.antennapod.audio.MediaPlayer;
import java.io.File;
import java.io.IOException;
+import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@@ -26,13 +26,17 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
import de.danoeh.antennapod.core.util.playback.AudioPlayer;
import de.danoeh.antennapod.core.util.playback.IPlayer;
import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.core.util.playback.VideoPlayer;
/**
@@ -308,7 +312,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
acquireWifiLockIfNecessary();
setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence());
- setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
+
+ float leftVolume = UserPreferences.getLeftVolume();
+ float rightVolume = UserPreferences.getRightVolume();
+ setVolume(leftVolume, rightVolume);
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind(
@@ -662,6 +669,15 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
*/
private void setVolumeSync(float volumeLeft, float volumeRight) {
playerLock.lock();
+ Playable playable = getPlayable();
+ if (playable instanceof FeedMedia) {
+ FeedMedia feedMedia = (FeedMedia) playable;
+ FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
+ VolumeAdaptionSetting volumeAdaptionSetting = preferences.getVolumeAdaptionSetting();
+ float adaptionFactor = volumeAdaptionSetting.getAdaptionFactor();
+ volumeLeft *= adaptionFactor;
+ volumeRight *= adaptionFactor;
+ }
mediaPlayer.setVolume(volumeLeft, volumeRight);
Log.d(TAG, "Media player volume was set to " + volumeLeft + " " + volumeRight);
playerLock.unlock();
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 b9afb5c75..fbd8d65d8 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
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.service.playback;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -51,12 +50,14 @@ import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.ServiceEvent;
+import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent;
+import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedComponent;
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.feed.SearchResult;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
@@ -70,7 +71,6 @@ import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -79,6 +79,10 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL;
/**
* Controls the MediaPlayer that plays a FeedMedia-file
@@ -151,7 +155,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public static final int EXTRA_CODE_CAST = 3;
public static final int NOTIFICATION_TYPE_ERROR = 0;
- public static final int NOTIFICATION_TYPE_INFO = 1;
public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
/**
@@ -277,6 +280,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
registerReceiver(audioBecomingNoisy, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE));
+ EventBus.getDefault().register(this);
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback);
@@ -746,10 +750,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
setupPositionUpdater();
stateManager.validStartCommandWasReceived();
// set sleep timer if auto-enabled
- if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
- SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
+ if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING
+ && SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
setSleepTimer(SleepTimerPreferences.timerMillis(), SleepTimerPreferences.shakeToReset(),
SleepTimerPreferences.vibrate());
+ EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_enabled_label),
+ PlaybackService.this::disableSleepTimer));
}
break;
@@ -1005,17 +1011,14 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
+ Log.d(TAG, "Setting sleep timer to " + waitingTime + " milliseconds");
taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
- EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_enabled_label),
- this::disableSleepTimer));
}
public void disableSleepTimer() {
taskManager.disableSleepTimer();
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
- EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_disabled_label)));
}
private void sendNotificationBroadcast(int type, int code) {
@@ -1325,7 +1328,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onReceive(Context context, Intent intent) {
- if (isInitialStickyBroadcast ()) {
+ if (isInitialStickyBroadcast()) {
// Don't pause playback after we just started, just because the receiver
// delivers the current headset state (instead of a change)
return;
@@ -1435,6 +1438,25 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
};
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void volumeAdaptionChanged(VolumeAdaptionChangedEvent event) {
+ PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater();
+ playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, event.getFeedId(), event.getVolumeAdaptionSetting());
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void speedPresetChanged(SpeedPresetChangedEvent event) {
+ if (getPlayable() instanceof FeedMedia) {
+ if (((FeedMedia) getPlayable()).getItem().getFeed().getId() == event.getFeedId()) {
+ if (event.getSpeed() == SPEED_USE_GLOBAL) {
+ setSpeed(UserPreferences.getPlaybackSpeed(getPlayable().getMediaType()));
+ } else {
+ setSpeed(event.getSpeed());
+ }
+ }
+ }
+ }
+
public static MediaType getCurrentMediaType() {
return currentMediaType;
}
@@ -1613,16 +1635,17 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onPlayFromSearch(String query, Bundle extras) {
Log.d(TAG, "onPlayFromSearch query=" + query + " extras=" + extras.toString());
- List<SearchResult> results = FeedSearcher.performSearch(getBaseContext(), query, 0);
- for (SearchResult result : results) {
- try {
- FeedMedia p = ((FeedItem) (result.getComponent())).getMedia();
- mediaPlayer.playMediaObject(p, !p.localFileAvailable(), true, true);
- return;
- } catch (Exception e) {
- Log.d(TAG, e.getMessage());
- e.printStackTrace();
- continue;
+ List<FeedComponent> results = FeedSearcher.performSearch(getBaseContext(), query, 0);
+ for (FeedComponent result : results) {
+ if (result instanceof FeedItem) {
+ try {
+ FeedMedia media = ((FeedItem) result).getMedia();
+ mediaPlayer.playMediaObject(media, !media.localFileAvailable(), true, true);
+ return;
+ } catch (Exception e) {
+ Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ }
}
}
onPlay();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java
index 6219fe78c..174b43846 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java
@@ -25,10 +25,11 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
-import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
+import java.util.ArrayList;
+import org.apache.commons.lang3.ArrayUtils;
public class PlaybackServiceNotificationBuilder {
private static final String TAG = "PlaybackSrvNotification";
@@ -151,7 +152,7 @@ public class PlaybackServiceNotificationBuilder {
private void addActions(NotificationCompat.Builder notification, MediaSessionCompat.Token mediaSessionToken,
PlayerStatus playerStatus, boolean isCasting) {
- IntList compactActionList = new IntList();
+ ArrayList<Integer> compactActionList = new ArrayList<>();
int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
@@ -218,7 +219,7 @@ public class PlaybackServiceNotificationBuilder {
KeyEvent.KEYCODE_MEDIA_STOP, numActions);
notification.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSessionToken)
- .setShowActionsInCompactView(compactActionList.toArray())
+ .setShowActionsInCompactView(ArrayUtils.toPrimitive(compactActionList.toArray(new Integer[0])))
.setShowCancelButton(true)
.setCancelButtonIntent(stopButtonPendingIntent));
}
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 736cf8cf2..62eda415e 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
@@ -397,41 +397,42 @@ public class PlaybackServiceTaskManager {
while (timeLeft > 0) {
try {
Thread.sleep(UPDATE_INTERVAL);
- long now = System.currentTimeMillis();
- timeLeft -= now - lastTick;
- lastTick = now;
-
- if(timeLeft < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
- Log.d(TAG, "Sleep timer is about to expire");
- if(vibrate) {
- Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- if(v != null) {
- v.vibrate(500);
- }
- }
- if(shakeListener == null && shakeToReset) {
- shakeListener = new ShakeListener(context, this);
- }
- postCallback(callback::onSleepTimerAlmostExpired);
- notifiedAlmostExpired = true;
- }
- if (timeLeft <= 0) {
- Log.d(TAG, "Sleep timer expired");
- if(shakeListener != null) {
- shakeListener.pause();
- shakeListener = null;
- }
- if (!Thread.currentThread().isInterrupted()) {
- postCallback(callback::onSleepTimerExpired);
- } else {
- Log.d(TAG, "Sleep timer interrupted");
- }
- }
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
e.printStackTrace();
break;
}
+
+ long now = System.currentTimeMillis();
+ timeLeft -= now - lastTick;
+ lastTick = now;
+
+ if (timeLeft < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
+ Log.d(TAG, "Sleep timer is about to expire");
+ if (vibrate) {
+ Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ if (v != null) {
+ v.vibrate(500);
+ }
+ }
+ if (shakeListener == null && shakeToReset) {
+ shakeListener = new ShakeListener(context, this);
+ }
+ postCallback(callback::onSleepTimerAlmostExpired);
+ notifiedAlmostExpired = true;
+ }
+ if (timeLeft <= 0) {
+ Log.d(TAG, "Sleep timer expired");
+ if (shakeListener != null) {
+ shakeListener.pause();
+ shakeListener = null;
+ }
+ if (!Thread.currentThread().isInterrupted()) {
+ postCallback(callback::onSleepTimerExpired);
+ } else {
+ Log.d(TAG, "Sleep timer interrupted");
+ }
+ }
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java
new file mode 100644
index 000000000..d03830387
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java
@@ -0,0 +1,36 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+class PlaybackVolumeUpdater {
+
+ public void updateVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, long feedId,
+ VolumeAdaptionSetting volumeAdaptionSetting) {
+ Playable playable = mediaPlayer.getPlayable();
+
+ if (playable instanceof FeedMedia) {
+ updateFeedMediaVolumeIfNecessary(mediaPlayer, feedId, volumeAdaptionSetting, (FeedMedia) playable);
+ }
+ }
+
+ private void updateFeedMediaVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, long feedId,
+ VolumeAdaptionSetting volumeAdaptionSetting, FeedMedia feedMedia) {
+ if (feedMedia.getItem().getFeed().getId() == feedId) {
+ FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
+ preferences.setVolumeAdaptionSetting(volumeAdaptionSetting);
+
+ if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
+ forceUpdateVolume(mediaPlayer);
+ }
+ }
+ }
+
+ private void forceUpdateVolume(PlaybackServiceMediaPlayer mediaPlayer) {
+ mediaPlayer.pause(false, false);
+ mediaPlayer.resume();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
index d029e7bfb..6e4054009 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
@@ -50,7 +50,7 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
List<FeedItem> candidates;
final List<FeedItem> queue = DBReader.getQueue();
- final List<FeedItem> newItems = DBReader.getNewItemsList();
+ final List<FeedItem> newItems = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
candidates = new ArrayList<>(queue.size() + newItems.size());
candidates.addAll(queue);
for (FeedItem newItem : newItems) {
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 8b87d7c54..4f4ee4721 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
@@ -353,16 +353,18 @@ public final class DBReader {
* Loads a list of FeedItems that are considered new.
* Excludes items from feeds that do not have keep updated enabled.
*
+ * @param offset The first episode that should be loaded.
+ * @param limit The maximum number of episodes that should be loaded.
* @return A list of FeedItems that are considered new.
*/
- public static List<FeedItem> getNewItemsList() {
+ public static List<FeedItem> getNewItemsList(int offset, int limit) {
Log.d(TAG, "getNewItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = null;
try {
- cursor = adapter.getNewItemsCursor();
+ cursor = adapter.getNewItemsCursor(offset, limit);
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
loadAdditionalFeedItemListData(items);
return items;
@@ -374,14 +376,21 @@ public final class DBReader {
}
}
- public static List<FeedItem> getFavoriteItemsList() {
+ /**
+ * Loads a list of favorite items.
+ *
+ * @param offset The first episode that should be loaded.
+ * @param limit The maximum number of episodes that should be loaded.
+ * @return A list of FeedItems that are marked as favorite.
+ */
+ public static List<FeedItem> getFavoriteItemsList(int offset, int limit) {
Log.d(TAG, "getFavoriteItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor cursor = null;
try {
- cursor = adapter.getFavoritesCursor();
+ cursor = adapter.getFavoritesCursor(offset, limit);
List<FeedItem> items = extractItemlistFromCursor(adapter, cursor);
loadAdditionalFeedItemListData(items);
return items;
@@ -400,7 +409,7 @@ public final class DBReader {
adapter.open();
Cursor cursor = null;
try {
- cursor = adapter.getFavoritesCursor();
+ cursor = adapter.getFavoritesCursor(0, Integer.MAX_VALUE);
LongList favoriteIDs = new LongList(cursor.getCount());
while (cursor.moveToNext()) {
favoriteIDs.add(cursor.getLong(0));
@@ -510,17 +519,6 @@ public final class DBReader {
/**
* Loads the download log for a particular feed from the database.
*
- * @param feed Feed for which the download log is loaded
- * @return A list with DownloadStatus objects that represent the feed's download log,
- * newest events first.
- */
- public static List<DownloadStatus> getFeedDownloadLog(Feed feed) {
- return getFeedDownloadLog(feed.getId());
- }
-
- /**
- * Loads the download log for a particular feed from the database.
- *
* @param feedId Feed id for which the download log is loaded
* @return A list with DownloadStatus objects that represent the feed's download log,
* newest events first.
@@ -691,7 +689,7 @@ public final class DBReader {
* Returns credentials based on image URL
*
* @param imageUrl The URL of the image
- * @return Credentials in format "<Username>:<Password>", empty String if no authorization given
+ * @return Credentials in format "Username:Password", empty String if no authorization given
*/
public static String getImageAuthentication(final String imageUrl) {
Log.d(TAG, "getImageAuthentication() called with: " + "imageUrl = [" + imageUrl + "]");
@@ -868,19 +866,15 @@ public final class DBReader {
}
/**
- * Searches the DB for statistics
+ * Searches the DB for statistics.
*
- * @param sortByCountAll If true, the statistic items will be sorted according to the
- * countAll calculation time
- * @return The StatisticsInfo object
+ * @return The list of statistics objects
*/
@NonNull
- public static StatisticsData getStatistics(boolean sortByCountAll) {
+ public static List<StatisticsItem> getStatistics() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
- long totalTimeCountAll = 0;
- long totalTime = 0;
List<StatisticsItem> feedTime = new ArrayList<>();
List<Feed> feeds = getFeedList();
@@ -891,6 +885,7 @@ public final class DBReader {
long episodes = 0;
long episodesStarted = 0;
long episodesStartedIncludingMarked = 0;
+ long totalDownloadSize = 0;
List<FeedItem> items = getFeed(feed.getId()).getItems();
for (FeedItem item : items) {
FeedMedia media = item.getMedia();
@@ -915,95 +910,20 @@ public final class DBReader {
}
feedTotalTime += media.getDuration() / 1000;
+
+ if (media.isDownloaded()) {
+ totalDownloadSize = totalDownloadSize + media.getSize();
+ }
+
episodes++;
}
feedTime.add(new StatisticsItem(
feed, feedTotalTime, feedPlayedTime, feedPlayedTimeCountAll, episodes,
- episodesStarted, episodesStartedIncludingMarked));
- totalTime += feedPlayedTime;
- totalTimeCountAll += feedPlayedTimeCountAll;
- }
-
- if (sortByCountAll) {
- Collections.sort(feedTime, (item1, item2) ->
- compareLong(item1.timePlayedCountAll, item2.timePlayedCountAll));
- } else {
- Collections.sort(feedTime, (item1, item2) ->
- compareLong(item1.timePlayed, item2.timePlayed));
+ episodesStarted, episodesStartedIncludingMarked, totalDownloadSize));
}
adapter.close();
- return new StatisticsData(totalTime, totalTimeCountAll, feedTime);
- }
-
- /**
- * Compares two {@code long} values. Long.compare() is not available before API 19
- *
- * @return 0 if long1 = long2, less than 0 if long1 &lt; long2,
- * and greater than 0 if long1 &gt; long2.
- */
- private static int compareLong(long long1, long long2) {
- if (long1 > long2) {
- return -1;
- } else if (long1 < long2) {
- return 1;
- } else {
- return 0;
- }
- }
-
- public static class StatisticsData {
- /**
- * Simply sums up time of podcasts that are marked as played
- */
- public final long totalTimeCountAll;
-
- /**
- * Respects speed, listening twice, ...
- */
- public final long totalTime;
-
- public final List<StatisticsItem> feedTime;
-
- public StatisticsData(long totalTime, long totalTimeCountAll, List<StatisticsItem> feedTime) {
- this.totalTime = totalTime;
- this.totalTimeCountAll = totalTimeCountAll;
- this.feedTime = feedTime;
- }
- }
-
- public static class StatisticsItem {
- public final Feed feed;
- public final long time;
-
- /**
- * Respects speed, listening twice, ...
- */
- public final long timePlayed;
- /**
- * Simply sums up time of podcasts that are marked as played
- */
- public final long timePlayedCountAll;
- public final long episodes;
- /**
- * Episodes that are actually played
- */
- public final long episodesStarted;
- /**
- * All episodes that are marked as played (or have position != 0)
- */
- public final long episodesStartedIncludingMarked;
-
- public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll,
- long episodes, long episodesStarted, long episodesStartedIncludingMarked) {
- this.feed = feed;
- this.time = time;
- this.timePlayed = timePlayed;
- this.timePlayedCountAll = timePlayedCountAll;
- this.episodes = episodes;
- this.episodesStarted = episodesStarted;
- this.episodesStartedIncludingMarked = episodesStartedIncludingMarked;
- }
+ return feedTime;
}
/**
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 c4a06efa2..8ebe18dc0 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
@@ -379,18 +379,6 @@ public final class DBTasks {
return result;
}
- /**
- * Loads the queue from the database and checks if the specified FeedItem is in the queue.
- * This method should NOT be executed in the GUI thread.
- *
- * @param context Used for accessing the DB.
- * @param feedItemId ID of the FeedItem
- */
- public static boolean isInQueue(Context context, final long feedItemId) {
- LongList queue = DBReader.getQueueIDList();
- return queue.contains(feedItemId);
- }
-
private static Feed searchFeedByIdentifyingValueOrID(PodDBAdapter adapter,
Feed feed) {
if (feed.getId() != 0) {
@@ -543,45 +531,20 @@ public final class DBTasks {
}
/**
- * Searches the titles of FeedItems of a specific Feed for a given
- * string.
- *
- * @param context Used for accessing the DB.
- * @param feedID The id of the feed whose items should be searched.
- * @param query The search string.
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
- */
- public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context,
- final long feedID, final String query) {
- return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemTitles(feedID,
- query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
- DBReader.loadAdditionalFeedItemListData(items);
- setResult(items);
- searchResult.close();
- }
- });
- }
-
- /**
- * Searches the authors of FeedItems of a specific Feed for a given
- * string.
+ * Searches the FeedItems of a specific Feed for a given string.
*
* @param context Used for accessing the DB.
* @param feedID The id of the feed whose items should be searched.
* @param query The search string.
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ * @return A FutureTask object that executes the search request
+ * and returns the search result as a List of FeedItems.
*/
- public static FutureTask<List<FeedItem>> searchFeedItemAuthor(final Context context,
+ public static FutureTask<List<FeedItem>> searchFeedItems(final Context context,
final long feedID, final String query) {
return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemAuthors(feedID,
- query);
+ Cursor searchResult = adapter.searchItems(feedID, query);
List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
DBReader.loadAdditionalFeedItemListData(items);
setResult(items);
@@ -590,97 +553,19 @@ public final class DBTasks {
});
}
- /**
- * Searches the feed identifiers of FeedItems of a specific Feed for a given
- * string.
- *
- * @param context Used for accessing the DB.
- * @param feedID The id of the feed whose items should be searched.
- * @param query The search string.
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
- */
- public static FutureTask<List<FeedItem>> searchFeedItemFeedIdentifier(final Context context,
- final long feedID, final String query) {
- return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
+ public static FutureTask<List<Feed>> searchFeeds(final Context context, final String query) {
+ return new FutureTask<>(new QueryTask<List<Feed>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemFeedIdentifiers(feedID,
- query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
- DBReader.loadAdditionalFeedItemListData(items);
- setResult(items);
- searchResult.close();
- }
- });
- }
-
- /**
- * Searches the descriptions of FeedItems of a specific Feed for a given
- * string.
- *
- * @param context Used for accessing the DB.
- * @param feedID The id of the feed whose items should be searched.
- * @param query The search string
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
- */
- public static FutureTask<List<FeedItem>> searchFeedItemDescription(final Context context,
- final long feedID, final String query) {
- return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemDescriptions(feedID,
- query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
- DBReader.loadAdditionalFeedItemListData(items);
- setResult(items);
- searchResult.close();
- }
- });
- }
-
- /**
- * Searches the contentEncoded-value of FeedItems of a specific Feed for a given
- * string.
- *
- * @param context Used for accessing the DB.
- * @param feedID The id of the feed whose items should be searched.
- * @param query The search string
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
- */
- public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context,
- final long feedID, final String query) {
- return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemContentEncoded(feedID,
- query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
- DBReader.loadAdditionalFeedItemListData(items);
- setResult(items);
- searchResult.close();
- }
- });
- }
-
- /**
- * Searches chapters of the FeedItems of a specific Feed for a given string.
- *
- * @param context Used for accessing the DB.
- * @param feedID The id of the feed whose items should be searched.
- * @param query The search string
- * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
- */
- public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context,
- final long feedID, final String query) {
- return new FutureTask<>(new QueryTask<List<FeedItem>>(context) {
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemChapters(feedID,
- query);
- List<FeedItem> items = DBReader.extractItemlistFromCursor(searchResult);
- DBReader.loadAdditionalFeedItemListData(items);
+ Cursor cursor = adapter.searchFeeds(query);
+ List<Feed> items = new ArrayList<>();
+ if (cursor.moveToFirst()) {
+ do {
+ items.add(Feed.fromCursor(cursor));
+ } while (cursor.moveToNext());
+ }
setResult(items);
- searchResult.close();
+ cursor.close();
}
});
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
index 0c8f89348..234ce8367 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
@@ -294,11 +294,14 @@ class DBUpgrader {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL);
}
-
if (oldVersion < 1070401) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_SORT_ORDER + " TEXT");
}
+ if (oldVersion < 1090000) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0");
+ }
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java
new file mode 100644
index 000000000..234c01b20
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java
@@ -0,0 +1,93 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import de.danoeh.antennapod.core.R;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+
+public class DatabaseExporter {
+ private static final String TAG = "DatabaseExporter";
+ private static final String TEMP_DB_NAME = PodDBAdapter.DATABASE_NAME + "_tmp";
+
+ public static void exportToDocument(Uri uri, Context context) throws IOException {
+ ParcelFileDescriptor pfd = null;
+ FileOutputStream fileOutputStream = null;
+ try {
+ pfd = context.getContentResolver().openFileDescriptor(uri, "w");
+ fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
+ exportToStream(fileOutputStream, context);
+ } catch (IOException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(fileOutputStream);
+
+ if (pfd != null) {
+ try {
+ pfd.close();
+ } catch (IOException e) {
+ Log.d(TAG, "Unable to close ParcelFileDescriptor");
+ }
+ }
+ }
+ }
+
+ public static void exportToStream(FileOutputStream outFileStream, Context context) throws IOException {
+ FileChannel src = null;
+ FileChannel dst = null;
+ try {
+ File currentDB = context.getDatabasePath(PodDBAdapter.DATABASE_NAME);
+
+ if (currentDB.exists()) {
+ src = new FileInputStream(currentDB).getChannel();
+ dst = outFileStream.getChannel();
+ dst.transferFrom(src, 0, src.size());
+ } else {
+ throw new IOException("Can not access current database");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(src);
+ IOUtils.closeQuietly(dst);
+ }
+ }
+
+ public static void importBackup(Uri inputUri, Context context) throws IOException {
+ InputStream inputStream = null;
+ try {
+ File tempDB = context.getDatabasePath(TEMP_DB_NAME);
+ inputStream = context.getContentResolver().openInputStream(inputUri);
+ FileUtils.copyInputStreamToFile(inputStream, tempDB);
+
+ SQLiteDatabase db = SQLiteDatabase.openDatabase(tempDB.getAbsolutePath(),
+ null, SQLiteDatabase.OPEN_READONLY);
+ if (db.getVersion() > PodDBAdapter.VERSION) {
+ throw new IOException(context.getString(R.string.import_no_downgrade));
+ }
+ db.close();
+
+ File currentDB = context.getDatabasePath(PodDBAdapter.DATABASE_NAME);
+ currentDB.delete();
+ FileUtils.moveFile(tempDB, currentDB);
+ } catch (IOException | SQLiteException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
index 0c8d20007..bbe8b26f1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
@@ -2,28 +2,22 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import androidx.annotation.NonNull;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedComponent;
+import de.danoeh.antennapod.core.feed.FeedItem;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.SearchResult;
-import de.danoeh.antennapod.core.util.comparator.InReverseChronologicalOrder;
-
/**
- * Performs search on Feeds and FeedItems
+ * Performs search on Feeds and FeedItems.
*/
public class FeedSearcher {
- private FeedSearcher(){}
-
- private static final String TAG = "FeedSearcher";
+ private FeedSearcher() {
+ }
/**
* Search through a feed, or all feeds, for episodes that match the query in either the title,
@@ -31,52 +25,25 @@ public class FeedSearcher {
* show notes. The list of resulting episodes also describes where the first match occurred
* (title, chapters, or show notes).
*
- * @param context
+ * @param context Used for database access
* @param query search query
* @param selectedFeed feed to search, 0 to search through all feeds
* @return list of episodes containing the query
*/
@NonNull
- public static List<SearchResult> performSearch(final Context context,
- final String query, final long selectedFeed) {
- final int values[] = {2, 1, 0, 0, 0, 0};
- final String[] subtitles = {context.getString(R.string.found_in_title_label),
- context.getString(R.string.found_in_chapters_label),
- context.getString(R.string.found_in_shownotes_label),
- context.getString(R.string.found_in_shownotes_label),
- context.getString(R.string.found_in_authors_label),
- context.getString(R.string.found_in_feeds_label)};
-
- final List<SearchResult> result = new ArrayList<>();
-
- List<FutureTask<List<FeedItem>>> tasks = new ArrayList<>();
- tasks.add(DBTasks.searchFeedItemTitle(context, selectedFeed, query));
- tasks.add(DBTasks.searchFeedItemChapters(context, selectedFeed, query));
- tasks.add(DBTasks.searchFeedItemDescription(context, selectedFeed, query));
- tasks.add(DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query));
- tasks.add(DBTasks.searchFeedItemAuthor(context, selectedFeed, query));
- tasks.add(DBTasks.searchFeedItemFeedIdentifier(context, selectedFeed, query));
-
- for (FutureTask<List<FeedItem>> task : tasks) {
- task.run();
- }
+ public static List<FeedComponent> performSearch(final Context context, final String query, final long selectedFeed) {
+ final List<FeedComponent> result = new ArrayList<>();
try {
- Set<Long> set = new HashSet<>();
+ FutureTask<List<FeedItem>> itemSearchTask = DBTasks.searchFeedItems(context, selectedFeed, query);
+ FutureTask<List<Feed>> feedSearchTask = DBTasks.searchFeeds(context, query);
+ itemSearchTask.run();
+ feedSearchTask.run();
- for (int i = 0; i < tasks.size(); i++) {
- FutureTask<List<FeedItem>> task = tasks.get(i);
- List<FeedItem> items = task.get();
- for (FeedItem item : items) {
- if (!set.contains(item.getId())) { // to prevent duplicate results
- result.add(new SearchResult(item, values[i], subtitles[i]));
- set.add(item.getId());
- }
- }
- }
+ result.addAll(feedSearchTask.get());
+ result.addAll(itemSearchTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
- Collections.sort(result, new InReverseChronologicalOrder());
return result;
}
}
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 1aff73564..4e2588f22 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
@@ -12,7 +12,6 @@ import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@@ -49,6 +48,7 @@ public class PodDBAdapter {
private static final String TAG = "PodDBAdapter";
public static final String DATABASE_NAME = "Antennapod.db";
+ public static final int VERSION = 1090000;
/**
* Maximum number of arguments for IN-operator.
@@ -97,6 +97,7 @@ public class PodDBAdapter {
public static final String KEY_AUTO_DOWNLOAD = "auto_download";
public static final String KEY_KEEP_UPDATED = "keep_updated";
public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action";
+ public static final String KEY_FEED_VOLUME_ADAPTION = "feed_volume_adaption";
public static final String KEY_PLAYED_DURATION = "played_duration";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
@@ -144,7 +145,8 @@ public class PodDBAdapter {
+ KEY_SORT_ORDER + " TEXT,"
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0,"
- + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ")";
+ + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ","
+ + KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0)";
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -241,6 +243,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_SORT_ORDER,
TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DELETE_ACTION,
+ TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_ADAPTION,
TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER,
TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER,
TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED
@@ -282,10 +285,13 @@ public class PodDBAdapter {
* Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries.
*/
private static final String SEL_FI_SMALL_STR;
+ private static final String FEED_SEL_STD_STR;
static {
String selFiSmall = Arrays.toString(FEEDITEM_SEL_FI_SMALL);
SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1);
+ String selFeedSmall = Arrays.toString(FEED_SEL_STD);
+ FEED_SEL_STD_STR = selFeedSmall.substring(1, selFeedSmall.length() - 1);
}
/**
@@ -403,6 +409,7 @@ public class PodDBAdapter {
values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload());
values.put(KEY_KEEP_UPDATED, prefs.getKeepUpdated());
values.put(KEY_AUTO_DELETE_ACTION, prefs.getAutoDeleteAction().ordinal());
+ values.put(KEY_FEED_VOLUME_ADAPTION, prefs.getVolumeAdaptionSetting().toInteger());
values.put(KEY_USERNAME, prefs.getUsername());
values.put(KEY_PASSWORD, prefs.getPassword());
values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter());
@@ -904,49 +911,6 @@ public class PodDBAdapter {
null, null);
}
- /**
- * Returns a cursor for a DB query in the FeedImages table for given IDs.
- *
- * @param imageIds IDs of the images
- * @return The cursor of the query
- */
- public final Cursor getImageCursor(String... imageIds) {
- int length = imageIds.length;
- if (length > IN_OPERATOR_MAXIMUM) {
- Log.w(TAG, "Length of id array is larger than "
- + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
- int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
- Cursor[] cursors = new Cursor[numCursors];
- for (int i = 0; i < numCursors; i++) {
- int neededLength;
- String[] parts;
- final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
-
- if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
- neededLength = IN_OPERATOR_MAXIMUM;
- parts = Arrays.copyOfRange(imageIds, i
- * IN_OPERATOR_MAXIMUM, (i + 1)
- * IN_OPERATOR_MAXIMUM);
- } else {
- neededLength = elementsLeft;
- parts = Arrays.copyOfRange(imageIds, i
- * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
- + neededLength);
- }
-
- cursors[i] = db.rawQuery("SELECT * FROM "
- + TABLE_NAME_FEED_IMAGES + " WHERE " + KEY_ID + " IN "
- + buildInOperator(neededLength), parts);
- }
- Cursor result = new MergeCursor(cursors);
- result.moveToFirst();
- return result;
- } else {
- return db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + " IN "
- + buildInOperator(length), imageIds, null, null, null);
- }
- }
-
public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
return db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
+ "=?", new String[]{String.valueOf(item.getId())}, null,
@@ -987,14 +951,17 @@ public class PodDBAdapter {
}
- public final Cursor getFavoritesCursor() {
+ public final Cursor getFavoritesCursor(int offset, int limit) {
Object[] args = new String[]{
SEL_FI_SMALL_STR,
TABLE_NAME_FEED_ITEMS, TABLE_NAME_FAVORITES,
TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
TABLE_NAME_FAVORITES + "." + KEY_FEEDITEM,
- TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE};
- String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s DESC", args);
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE,
+ String.valueOf(offset),
+ String.valueOf(limit)
+ };
+ String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s DESC LIMIT %s, %s", args);
return db.rawQuery(query, null);
}
@@ -1027,16 +994,19 @@ public class PodDBAdapter {
* Excludes those feeds that do not have 'Keep Updated' enabled.
* The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
*/
- public final Cursor getNewItemsCursor() {
+ public final Cursor getNewItemsCursor(int offset, int limit) {
Object[] args = new String[]{
SEL_FI_SMALL_STR,
TABLE_NAME_FEED_ITEMS,
TABLE_NAME_FEEDS,
TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID,
TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.NEW + " AND " + TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED + " > 0",
- KEY_PUBDATE + " DESC"
+ KEY_PUBDATE + " DESC",
+ String.valueOf(offset),
+ String.valueOf(limit)
};
- final String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s WHERE %s ORDER BY %s", args);
+ final String query = String.format("SELECT %s FROM %s INNER JOIN %s ON %s WHERE %s "
+ + "ORDER BY %s LIMIT %s, %s", args);
return db.rawQuery(query, null);
}
@@ -1290,133 +1260,47 @@ public class PodDBAdapter {
}
/**
- * Searches for the given query in the description of all items or the items
+ * Searches for the given query in various values of all items or the items
* of a specified feed.
*
* @return A cursor with all search results in SEL_FI_EXTRA selection.
*/
- public Cursor searchItemDescriptions(long feedID, String query) {
+ public Cursor searchItems(long feedID, String searchQuery) {
+ String preparedQuery = prepareSearchQuery(searchQuery);
+
+ String queryFeedId = "";
if (feedID != 0) {
// search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
- + "=? AND " + KEY_DESCRIPTION + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[]{String.valueOf(feedID)}, null, null,
- null
- );
+ queryFeedId = KEY_FEED + " = " + feedID;
} else {
// search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL,
- KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query)
- + "%'", null, null, null, null
- );
+ queryFeedId = "1 = 1";
}
+
+ String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " WHERE " + queryFeedId + " AND ("
+ + KEY_DESCRIPTION + " LIKE '%" + preparedQuery + "%' OR "
+ + KEY_CONTENT_ENCODED + " LIKE '%" + preparedQuery + "%' OR "
+ + KEY_TITLE + " LIKE '%" + preparedQuery + "%'"
+ + ") ORDER BY " + KEY_PUBDATE + " DESC "
+ + "LIMIT 300";
+ return db.rawQuery(query, null);
}
/**
- * Searches for the given query in the content-encoded field of all items or
- * the items of a specified feed.
+ * Searches for the given query in various values of all feeds.
*
* @return A cursor with all search results in SEL_FI_EXTRA selection.
*/
- public Cursor searchItemContentEncoded(long feedID, String query) {
- if (feedID != 0) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
- + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[]{String.valueOf(feedID)}, null, null,
- null
- );
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL,
- KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'", null, null,
- null, null
- );
- }
- }
-
- public Cursor searchItemTitles(long feedID, String query) {
- if (feedID != 0) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
- + "=? AND " + KEY_TITLE + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[]{String.valueOf(feedID)}, null, null,
- null
- );
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL,
- KEY_TITLE + " LIKE '%"
- + prepareSearchQuery(query) + "%'", null, null,
- null, null
- );
- }
- }
-
- public Cursor searchItemAuthors(long feedID, String query) {
- if (feedID != 0) {
- // search items in specific feed
- return db.rawQuery("SELECT " + TextUtils.join(", ", FEEDITEM_SEL_FI_SMALL) + " FROM " + TABLE_NAME_FEED_ITEMS
- + " JOIN " + TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + KEY_FEED
- + "=? AND " + KEY_AUTHOR + " LIKE '%"
- + prepareSearchQuery(query) + "%' ORDER BY "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " DESC",
- new String[]{String.valueOf(feedID)}
- );
- } else {
- // search through all items
- return db.rawQuery("SELECT " + TextUtils.join(", ", FEEDITEM_SEL_FI_SMALL) + " FROM " + TABLE_NAME_FEED_ITEMS
- + " JOIN " + TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + KEY_AUTHOR + " LIKE '%"
- + prepareSearchQuery(query) + "%' ORDER BY "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " DESC",
- null
- );
- }
- }
-
- public Cursor searchItemFeedIdentifiers(long feedID, String query) {
- if (feedID != 0) {
- // search items in specific feed
- return db.rawQuery("SELECT " + TextUtils.join(", ", FEEDITEM_SEL_FI_SMALL) + " FROM " + TABLE_NAME_FEED_ITEMS
- + " JOIN " + TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + KEY_FEED
- + "=? AND " + KEY_FEED_IDENTIFIER + " LIKE '%"
- + prepareSearchQuery(query) + "%' ORDER BY "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " DESC",
- new String[]{String.valueOf(feedID)}
- );
- } else {
- // search through all items
- return db.rawQuery("SELECT " + TextUtils.join(", ", FEEDITEM_SEL_FI_SMALL) + " FROM " + TABLE_NAME_FEED_ITEMS
- + " JOIN " + TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + KEY_FEED_IDENTIFIER + " LIKE '%"
- + prepareSearchQuery(query) + "%' ORDER BY "
- + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " DESC",
- null
- );
- }
- }
-
- public Cursor searchItemChapters(long feedID, String searchQuery) {
- final String query;
- if (feedID != 0) {
- query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
- TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
- TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" +
- feedID + " AND " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
- + prepareSearchQuery(searchQuery) + "%'";
- } else {
- query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
- TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
- TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
- + prepareSearchQuery(searchQuery) + "%'";
- }
+ public Cursor searchFeeds(String searchQuery) {
+ String preparedQuery = prepareSearchQuery(searchQuery);
+ String query = "SELECT " + FEED_SEL_STD_STR + " FROM " + TABLE_NAME_FEEDS + " WHERE "
+ + KEY_TITLE + " LIKE '%" + preparedQuery + "%' OR "
+ + KEY_CUSTOM_TITLE + " LIKE '%" + preparedQuery + "%' OR "
+ + KEY_AUTHOR + " LIKE '%" + preparedQuery + "%' OR "
+ + KEY_DESCRIPTION + " LIKE '%" + preparedQuery + "%' "
+ + "ORDER BY " + KEY_TITLE + " ASC "
+ + "LIMIT 300";
return db.rawQuery(query, null);
}
@@ -1464,11 +1348,6 @@ public class PodDBAdapter {
* Helper class for opening the Antennapod database.
*/
private static class PodDBHelper extends SQLiteOpenHelper {
-
- private static final int VERSION = 1070401;
-
- private final Context context;
-
/**
* Constructor.
*
@@ -1476,10 +1355,8 @@ public class PodDBAdapter {
* @param name Name of the database
* @param factory to use for creating cursor objects
*/
- public PodDBHelper(final Context context, final String name,
- final CursorFactory factory) {
+ public PodDBHelper(final Context context, final String name, final CursorFactory factory) {
super(context, name, factory, VERSION, new PodDbErrorHandler());
- this.context = context;
}
@Override
@@ -1498,14 +1375,11 @@ public class PodDBAdapter {
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
-
}
@Override
- public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
- final int newVersion) {
- Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
- + newVersion + ".");
+ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to " + newVersion + ".");
DBUpgrader.upgrade(db, oldVersion, newVersion);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java
new file mode 100644
index 000000000..f96af185b
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java
@@ -0,0 +1,51 @@
+package de.danoeh.antennapod.core.storage;
+
+import de.danoeh.antennapod.core.feed.Feed;
+
+public class StatisticsItem {
+ public final Feed feed;
+ public final long time;
+
+ /**
+ * Respects speed, listening twice, ...
+ */
+ public final long timePlayed;
+
+ /**
+ * Simply sums up time of podcasts that are marked as played.
+ */
+ public final long timePlayedCountAll;
+
+ /**
+ * Number of episodes.
+ */
+ public final long episodes;
+
+ /**
+ * Episodes that are actually played.
+ */
+ public final long episodesStarted;
+
+ /**
+ * All episodes that are marked as played (or have position != 0).
+ */
+ public final long episodesStartedIncludingMarked;
+
+ /**
+ * Simply sums up the size of download podcasts.
+ */
+ public final long totalDownloadSize;
+
+ public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll,
+ long episodes, long episodesStarted, long episodesStartedIncludingMarked,
+ long totalDownloadSize) {
+ this.feed = feed;
+ this.time = time;
+ this.timePlayed = timePlayed;
+ this.timePlayedCountAll = timePlayedCountAll;
+ this.episodes = episodes;
+ this.episodesStarted = episodesStarted;
+ this.episodesStartedIncludingMarked = episodesStartedIncludingMarked;
+ this.totalDownloadSize = totalDownloadSize;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
deleted file mode 100644
index 1da5417c1..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package de.danoeh.antennapod.core.util;
-
-import java.util.Arrays;
-
-/**
- * Fast and memory efficient int list
- */
-public final class IntList {
-
- private int[] values;
- private int size;
-
- /**
- * Constructs an empty instance with a default initial capacity.
- */
- public IntList() {
- this(4);
- }
-
- /**
- * Constructs an empty instance.
- *
- * @param initialCapacity {@code >= 0;} initial capacity of the list
- */
- private IntList(int initialCapacity) {
- if(initialCapacity < 0) {
- throw new IllegalArgumentException("initial capacity must be 0 or higher");
- }
- values = new int[initialCapacity];
- size = 0;
- }
-
- @Override
- public int hashCode() {
- int hashCode = 1;
- for (int i = 0; i < size; i++) {
- int value = values[i];
- hashCode = 31 * hashCode + value;
- }
- return hashCode;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- if (! (other instanceof IntList)) {
- return false;
- }
- IntList otherList = (IntList) other;
- if (size != otherList.size) {
- return false;
- }
- for (int i = 0; i < size; i++) {
- if (values[i] != otherList.values[i]) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(size * 5 + 10);
- sb.append("IntList{");
- for (int i = 0; i < size; i++) {
- if (i != 0) {
- sb.append(", ");
- }
- sb.append(values[i]);
- }
- sb.append("}");
- return sb.toString();
- }
-
- /**
- * Gets the number of elements in this list.
- */
- public int size() {
- return size;
- }
-
- /**
- * Gets the indicated value.
- *
- * @param n {@code >= 0, < size();} which element
- * @return the indicated element's value
- */
- public int get(int n) {
- if (n >= size) {
- throw new IndexOutOfBoundsException("n >= size()");
- } else if(n < 0) {
- throw new IndexOutOfBoundsException("n < 0");
- }
- return values[n];
- }
-
- /**
- * Sets the value at the given index.
- *
- * @param index the index at which to put the specified object.
- * @param value the object to add.
- * @return the previous element at the index.
- */
- public int set(int index, int value) {
- if (index >= size) {
- throw new IndexOutOfBoundsException("n >= size()");
- } else if(index < 0) {
- throw new IndexOutOfBoundsException("n < 0");
- }
- int result = values[index];
- values[index] = value;
- return result;
- }
-
- /**
- * Adds an element to the end of the list. This will increase the
- * list's capacity if necessary.
- *
- * @param value the value to add
- */
- public void add(int value) {
- growIfNeeded();
- values[size++] = value;
- }
-
- /**
- * Inserts element into specified index, moving elements at and above
- * that index up one. May not be used to insert at an index beyond the
- * current size (that is, insertion as a last element is legal but
- * no further).
- *
- * @param n {@code >= 0, <=size();} index of where to insert
- * @param value value to insert
- */
- public void insert(int n, int value) {
- if (n > size) {
- throw new IndexOutOfBoundsException("n > size()");
- } else if(n < 0) {
- throw new IndexOutOfBoundsException("n < 0");
- }
-
- growIfNeeded();
-
- System.arraycopy (values, n, values, n+1, size - n);
- values[n] = value;
- size++;
- }
-
- /**
- * Removes value from this list.
- *
- * @param value value to remove
- * return {@code true} if the value was removed, {@code false} otherwise
- */
- public boolean remove(int value) {
- for (int i = 0; i < size; i++) {
- if (values[i] == value) {
- size--;
- System.arraycopy(values, i+1, values, i, size-i);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Removes an element at a given index, shifting elements at greater
- * indicies down one.
- *
- * @param index index of element to remove
- */
- public void removeIndex(int index) {
- if (index >= size) {
- throw new IndexOutOfBoundsException("n >= size()");
- } else if(index < 0) {
- throw new IndexOutOfBoundsException("n < 0");
- }
- size--;
- System.arraycopy (values, index + 1, values, index, size - index);
- }
-
- /**
- * Increases size of array if needed
- */
- private void growIfNeeded() {
- if (size == values.length) {
- // Resize.
- int[] newArray = new int[size * 3 / 2 + 10];
- System.arraycopy(values, 0, newArray, 0, size);
- values = newArray;
- }
- }
-
- /**
- * Returns the index of the given value, or -1 if the value does not
- * appear in the list.
- *
- * @param value value to find
- * @return index of value or -1
- */
- private int indexOf(int value) {
- for (int i = 0; i < size; i++) {
- if (values[i] == value) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Removes all values from this list.
- */
- public void clear() {
- values = new int[4];
- size = 0;
- }
-
-
- /**
- * Returns true if the given value is contained in the list
- *
- * @param value value to look for
- * @return {@code true} if this list contains {@code value}, {@code false} otherwise
- */
- public boolean contains(int value) {
- return indexOf(value) >= 0;
- }
-
- /**
- * Returns an array with a copy of this list's values
- *
- * @return array with a copy of this list's values
- */
- public int[] toArray() {
- return Arrays.copyOf(values, size);
-
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
index 656b518bf..959a3e574 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
@@ -38,6 +38,7 @@ public class IntentUtils {
public static void openInBrowser(Context context, String url) {
try {
Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.pref_no_browser_found, Toast.LENGTH_LONG).show();
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
index fdc244517..742920702 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -154,7 +154,7 @@ public final class LongList {
growIfNeeded();
- System.arraycopy (values, n, values, n+1, size - n);
+ System.arraycopy(values, n, values, n+1, size - n);
values[n] = value;
size++;
}
@@ -211,7 +211,7 @@ public final class LongList {
throw new IndexOutOfBoundsException("n < 0");
}
size--;
- System.arraycopy (values, index + 1, values, index, size - index);
+ System.arraycopy(values, index + 1, values, index, size - index);
}
/**
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 9408be348..ec8b8a430 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
@@ -26,36 +26,4 @@ public abstract class QueueAccess {
private QueueAccess() {
}
-
- public static QueueAccess ItemListAccess(final List<FeedItem> items) {
- return new QueueAccess() {
- @Override
- public boolean contains(long id) {
- if (items == null) {
- return false;
- }
- for (FeedItem item : items) {
- if (item.getId() == id) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean remove(long id) {
- Iterator<FeedItem> it = items.iterator();
- FeedItem item;
- while (it.hasNext()) {
- item = it.next();
- if (item.getId() == id) {
- it.remove();
- return true;
- }
- }
- return false;
- }
- };
- }
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/TimeSpeedConverter.java b/core/src/main/java/de/danoeh/antennapod/core/util/TimeSpeedConverter.java
index 5d44c14b8..bbfe528be 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/TimeSpeedConverter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/TimeSpeedConverter.java
@@ -10,7 +10,7 @@ public class TimeSpeedConverter {
}
/** Convert millisecond according to the current playback speed
- * @param time: time to convert
+ * @param time time to convert
* @return converted time (can be < 0 if time is < 0)
*/
public int convert(int time) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
index 7e3870a20..dbdb37c3b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
@@ -1,10 +1,16 @@
package de.danoeh.antennapod.core.util;
import android.net.Uri;
+import android.text.TextUtils;
import androidx.annotation.NonNull;
import android.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
+import okhttp3.HttpUrl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Provides methods for checking and editing a URL.
@@ -78,4 +84,45 @@ public final class URLChecker {
return prepareURL(url);
}
}
+
+ public static boolean containsUrl(List<String> list, String url) {
+ for (String item : list) {
+ if (urlEquals(item, url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean urlEquals(String string1, String string2) {
+ HttpUrl url1 = HttpUrl.parse(string1);
+ HttpUrl url2 = HttpUrl.parse(string2);
+ if (!url1.host().equals(url2.host())) {
+ return false;
+ }
+ List<String> pathSegments1 = normalizePathSegments(url1.pathSegments());
+ List<String> pathSegments2 = normalizePathSegments(url2.pathSegments());
+ if (!pathSegments1.equals(pathSegments2)) {
+ return false;
+ }
+ if (TextUtils.isEmpty(url1.query())) {
+ return TextUtils.isEmpty(url2.query());
+ }
+ return url1.query().equals(url2.query());
+ }
+
+ /**
+ * Removes empty segments and converts all to lower case.
+ * @param input List of path segments
+ * @return Normalized list of path segments
+ */
+ private static List<String> normalizePathSegments(List<String> input) {
+ List<String> result = new ArrayList<>();
+ for (String string : input) {
+ if (!TextUtils.isEmpty(string)) {
+ result.add(string.toLowerCase());
+ }
+ }
+ return result;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/CompareCompat.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/CompareCompat.java
new file mode 100644
index 000000000..c189f2389
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/CompareCompat.java
@@ -0,0 +1,29 @@
+package de.danoeh.antennapod.core.util.comparator;
+
+/**
+ * Some compare() methods are not available before API 19.
+ * This class provides fallbacks
+ */
+public class CompareCompat {
+
+ private CompareCompat() {
+ // Must not be instantiated
+ }
+
+ /**
+ * Compares two {@code long} values. Long.compare() is not available before API 19
+ *
+ * @return 0 if long1 = long2, less than 0 if long1 &lt; long2,
+ * and greater than 0 if long1 &gt; long2.
+ */
+ public static int compareLong(long long1, long long2) {
+ //noinspection UseCompareMethod
+ if (long1 > long2) {
+ return -1;
+ } else if (long1 < long2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/InReverseChronologicalOrder.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/InReverseChronologicalOrder.java
deleted file mode 100644
index 80246af8f..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/InReverseChronologicalOrder.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.danoeh.antennapod.core.util.comparator;
-
-import java.util.Comparator;
-
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.SearchResult;
-
-public class InReverseChronologicalOrder implements Comparator<SearchResult> {
- /**
- * Compare items and sort it on chronological order.
- */
- @Override
- public int compare(SearchResult o1, SearchResult o2) {
- if ((o1.getComponent() instanceof FeedItem) && (o2.getComponent() instanceof FeedItem)) {
- FeedItem item1 = (FeedItem) o1.getComponent();
- FeedItem item2 = (FeedItem) o2.getComponent();
- return item2.getPubDate().compareTo(item1.getPubDate());
- }
- return 0;
- }
-}
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 e7f6ad4f1..cb22fbcc9 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
@@ -628,10 +628,6 @@ public class PlaybackController {
return playbackService != null && playbackService.sleepTimerActive();
}
- public boolean sleepTimerNotActive() {
- return playbackService != null && !playbackService.sleepTimerActive();
- }
-
public void disableSleepTimer() {
if (playbackService != null) {
playbackService.disableSleepTimer();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
index 5b9452d6f..0fd658853 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
@@ -9,11 +9,15 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import org.apache.commons.io.IOUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -27,48 +31,20 @@ import de.danoeh.antennapod.core.util.ShownotesProvider;
* shownotes to navigate to another position in the podcast or by highlighting certain parts of the shownotesProvider's
* shownotes.
* <p/>
- * A timeline object needs a shownotesProvider from which the chapter information is retrieved and shownotes are generated.
+ * A timeline object needs a shownotesProvider from which the chapter information
+ * is retrieved and shownotes are generated.
*/
public class Timeline {
private static final String TAG = "Timeline";
- private static final String WEBVIEW_STYLE =
- "@font-face {"
- + "font-family: 'Roboto-Light';"
- + "src: url('file:///android_asset/Roboto-Light.ttf');"
- + "}"
- + "* {"
- + "color: %s;"
- + "font-family: roboto-Light;"
- + "font-size: 13pt;"
- + "overflow-wrap: break-word;"
- + "}"
- + "a {"
- + "font-style: normal;"
- + "text-decoration: none;"
- + "font-weight: normal;"
- + "color: #00A8DF;"
- + "}"
- + "a.timecode {"
- + "color: #669900;"
- + "}"
- + "img, iframe {"
- + "display: block;"
- + "margin: 10 auto;"
- + "max-width: %s;"
- + "height: auto;"
- + "}"
- + "body {"
- + "margin: %dpx %dpx %dpx %dpx;"
- + "}";
-
-
- private ShownotesProvider shownotesProvider;
+ private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
+ private static final String TIMECODE_LINK = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
+ private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b");
+ private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
+ private final ShownotesProvider shownotesProvider;
private final String noShownotesLabel;
- private final String colorPrimaryString;
- private final String colorSecondaryString;
- private final int pageMargin;
+ private final String webviewStyle;
public Timeline(Context context, ShownotesProvider shownotesProvider) {
if (shownotesProvider == null) {
@@ -80,41 +56,31 @@ public class Timeline {
TypedArray res = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorPrimary});
@ColorInt int col = res.getColor(0, 0);
- colorPrimaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
- Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")";
+ final String colorPrimary = "rgba(" + Color.red(col) + "," + Color.green(col) + ","
+ + Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")";
res.recycle();
- res = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorSecondary});
- col = res.getColor(0, 0);
- colorSecondaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
- Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")";
- res.recycle();
-
- pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
- context.getResources().getDisplayMetrics()
- );
+ final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
+ context.getResources().getDisplayMetrics());
+ String styleString = "";
+ try {
+ InputStream templateStream = context.getAssets().open("shownotes-style.css");
+ styleString = IOUtils.toString(templateStream, "UTF-8");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ webviewStyle = String.format(Locale.getDefault(), styleString, colorPrimary, margin, margin, margin, margin);
}
- private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
- private static final String TIMECODE_LINK = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
- private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b");
- private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
-
-
/**
* Applies an app-specific CSS stylesheet and adds timecode links (optional).
* <p/>
* This method does NOT change the original shownotes string of the shownotesProvider object and it should
* also not be changed by the caller.
*
- * @param addTimecodes True if this method should add timecode links
* @return The processed HTML string.
*/
@NonNull
- public String processShownotes(final boolean addTimecodes) {
- final Playable playable = (shownotesProvider instanceof Playable) ? (Playable) shownotesProvider : null;
-
- // load shownotes
-
+ public String processShownotes() {
String shownotes;
try {
shownotes = shownotesProvider.loadShownotes().call();
@@ -125,21 +91,7 @@ public class Timeline {
if (TextUtils.isEmpty(shownotes)) {
Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message");
- shownotes = "<html>" +
- "<head>" +
- "<style type='text/css'>" +
- "html, body { margin: 0; padding: 0; width: 100%; height: 100%; } " +
- "html { display: table; }" +
- "body { display: table-cell; vertical-align: middle; text-align:center;" +
- "-webkit-text-size-adjust: none; font-size: 87%; color: " + colorSecondaryString + ";} " +
- "</style>" +
- "</head>" +
- "<body>" +
- "<p>" + noShownotesLabel + "</p>" +
- "</body>" +
- "</html>";
- Log.d(TAG, "shownotes: " + shownotes);
- return shownotes;
+ shownotes = "<html><head></head><body><p id='apNoShownotes'>" + noShownotesLabel + "</p></body></html>";
}
// replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already
@@ -148,17 +100,10 @@ public class Timeline {
}
Document document = Jsoup.parse(shownotes);
-
- // apply style
- String styleStr = String.format(Locale.getDefault(), WEBVIEW_STYLE, colorPrimaryString, "100%",
- pageMargin, pageMargin, pageMargin, pageMargin);
- document.head().appendElement("style").attr("type", "text/css").text(styleStr);
+ document.head().appendElement("style").attr("type", "text/css").text(webviewStyle);
// apply timecode links
- if (addTimecodes) {
- addTimecodes(document, playable);
- }
-
+ addTimecodes(document);
return document.toString();
}
@@ -188,12 +133,7 @@ public class Timeline {
return -1;
}
-
- public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
- this.shownotesProvider = shownotesProvider;
- }
-
- private void addTimecodes(Document document, final Playable playable) {
+ private void addTimecodes(Document document) {
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
@@ -202,7 +142,13 @@ public class Timeline {
return;
}
- int playableDuration = playable == null ? Integer.MAX_VALUE : playable.getDuration();
+ int playableDuration = Integer.MAX_VALUE;
+ if (shownotesProvider instanceof Playable) {
+ playableDuration = ((Playable) shownotesProvider).getDuration();
+ } else if (shownotesProvider instanceof FeedItem && ((FeedItem) shownotesProvider).getMedia() != null) {
+ playableDuration = ((FeedItem) shownotesProvider).getMedia().getDuration();
+ }
+
boolean useHourFormat = true;
if (playableDuration != Integer.MAX_VALUE) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
index 3550f28c6..84d98a905 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
@@ -57,7 +57,7 @@ public class HtmlToPlainText {
* Use this method to determine if a given text has any HTML tag
*
* @param str String to be tested for presence of HTML content
- * @return <b>True</b> if text contains any HTML tags</br><b>False</b> is no HTML tag is found
+ * @return <b>True</b> if text contains any HTML tags<br /><b>False</b> is no HTML tag is found
*/
private static boolean isHtml(String str) {
final String HTML_TAG_PATTERN = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>";
diff --git a/core/src/main/res/drawable/borderless_button_dark.xml b/core/src/main/res/drawable/borderless_button_dark.xml
deleted file mode 100644
index 3a44d81a2..000000000
--- a/core/src/main/res/drawable/borderless_button_dark.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="@color/selection_background_color_dark" />
- </shape>
- </item>
- <item android:state_focused="true">
- <shape android:shape="rectangle">
- <solid android:color="@color/selection_background_color_dark" />
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent" />
- </shape>
- </item>
-</selector> \ No newline at end of file
diff --git a/core/src/main/res/drawable/ic_volume_adaption_grey.xml b/core/src/main/res/drawable/ic_volume_adaption_grey.xml
new file mode 100644
index 000000000..fe39e1c70
--- /dev/null
+++ b/core/src/main/res/drawable/ic_volume_adaption_grey.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF757575"
+ android:pathData="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" />
+</vector> \ No newline at end of file
diff --git a/core/src/main/res/drawable/ic_volume_adaption_white.xml b/core/src/main/res/drawable/ic_volume_adaption_white.xml
new file mode 100644
index 000000000..27d7c6e7b
--- /dev/null
+++ b/core/src/main/res/drawable/ic_volume_adaption_white.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" />
+</vector> \ No newline at end of file
diff --git a/core/src/main/res/values-v16/colors.xml b/core/src/main/res/values-v16/colors.xml
deleted file mode 100644
index 4154280e8..000000000
--- a/core/src/main/res/values-v16/colors.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="selection_background_color_dark">#484B4D</color>
-</resources> \ No newline at end of file
diff --git a/core/src/main/res/values-v16/styles.xml b/core/src/main/res/values-v16/styles.xml
deleted file mode 100644
index 947e43f38..000000000
--- a/core/src/main/res/values-v16/styles.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <style name="AntennaPod.TextView.Heading" parent="@android:style/TextAppearance.Medium">
- <item name="android:textSize">@dimen/text_size_large</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-</resources> \ No newline at end of file
diff --git a/core/src/main/res/values-v26/styles.xml b/core/src/main/res/values-v26/styles.xml
new file mode 100644
index 000000000..87453eb5e
--- /dev/null
+++ b/core/src/main/res/values-v26/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Theme.AntennaPod.Splash" parent="Theme.Base.AntennaPod.Splash">
+ <item name="android:windowSplashscreenContent">@drawable/bg_splash</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index facde2c59..dc79905cd 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -13,6 +13,18 @@
<item>never</item>
</string-array>
+ <string-array name="spnVolumeReductionItems">
+ <item>@string/feed_volume_reduction_off</item>
+ <item>@string/feed_volume_reduction_light</item>
+ <item>@string/feed_volume_reduction_heavy</item>
+ </string-array>
+
+ <string-array name="spnVolumeReductionValues">
+ <item>off</item>
+ <item>light</item>
+ <item>heavy</item>
+ </string-array>
+
<string-array name="smart_mark_as_played_values">
<item>0</item>
<item>15</item>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index fe571539a..53cf7b211 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -28,7 +28,6 @@
<attr name="ic_folder" format="reference"/>
<attr name="type_audio" format="reference"/>
<attr name="type_video" format="reference"/>
- <attr name="borderless_button" format="reference"/>
<attr name="overlay_drawable" format="reference"/>
<attr name="dragview_background" format="reference"/>
<attr name="dragview_float_background" format="reference"/>
@@ -53,6 +52,7 @@
<attr name="ic_select_none" format="reference"/>
<attr name="ic_sort" format="reference"/>
<attr name="ic_key" format="reference"/>
+ <attr name="ic_volume_adaption" format="reference"/>
<attr name="ic_sd_storage" format="reference"/>
<attr name="ic_create_new_folder" format="reference"/>
<attr name="ic_cast_disconnect" format="reference"/>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index ce6a0e41d..e5d8247bc 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -7,7 +7,7 @@
<string name="app_name" translate="false">AntennaPod</string>
<string name="provider_authority" translate="false">de.danoeh.antennapod.provider</string>
<string name="feed_update_receiver_name">Update Subscriptions</string>
- <string name="feeds_label">Feeds</string>
+ <string name="feeds_label">Podcasts</string>
<string name="statistics_label">Statistics</string>
<string name="add_feed_label">Add Podcast</string>
<string name="episodes_label">Episodes</string>
@@ -29,6 +29,8 @@
<string name="gpodnet_auth_label">gpodder.net Login</string>
<string name="episode_cache_full_title">Episode cache full</string>
<string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string>
+ <string name="playback_statistics_label">Playback</string>
+ <string name="download_statistics_label">Downloads</string>
<!-- Statistics fragment -->
<string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
@@ -40,6 +42,9 @@
<string name="statistics_reset_data">Reset statistics data</string>
<string name="statistics_reset_data_msg">This will erase the history of duration played for all episodes. Are you sure you want to proceed?</string>
+ <!-- Download Statistics fragment -->
+ <string name="total_size_downloaded_podcasts">Total size of downloaded podcasts:</string>
+
<!-- Main activity -->
<string name="drawer_open">Open menu</string>
<string name="drawer_close">Close menu</string>
@@ -91,6 +96,11 @@
<string name="auto_download_apply_to_items_title">Apply to Previous Episodes</string>
<string name="auto_download_apply_to_items_message">The new <i>Auto Download</i> setting will automatically be applied to new episodes.\nDo you also want to apply it to previously published episodes?</string>
<string name="auto_delete_label">Auto Delete Episode</string>
+ <string name="feed_volume_reduction">Volume Reduction</string>
+ <string name="feed_volume_reduction_summary">Turn down volume for episodes of this feed: \%s</string>
+ <string name="feed_volume_reduction_off">Off</string>
+ <string name="feed_volume_reduction_light">Light</string>
+ <string name="feed_volume_reduction_heavy">Heavy</string>
<string name="parallel_downloads_suffix">\u0020parallel downloads</string>
<string name="feed_auto_download_global">Global default</string>
<string name="feed_auto_download_always">Always</string>
@@ -108,9 +118,10 @@
<item quantity="other">%d days after finishing</item>
</plurals>
<string name="num_selected_label">%d selected</string>
+ <string name="loading_more">Loading more…</string>
<!-- 'Add Feed' Activity labels -->
- <string name="feedurl_label">Feed URL</string>
+ <string name="feedurl_label">Podcast feed URL</string>
<string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">Add Podcast by URL</string>
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
@@ -136,7 +147,7 @@
<string name="share_link_with_position_label">Share Episode URL with Position</string>
<string name="share_file_label">Share File</string>
<string name="share_website_url_label">Share Website URL</string>
- <string name="share_feed_url_label">Share Feed URL</string>
+ <string name="share_feed_url_label">Share Podcast URL</string>
<string name="share_item_url_label">Share Media File URL</string>
<string name="share_item_url_with_position_label">Share Media File URL with Position</string>
<string name="feed_delete_confirmation_msg">Please confirm that you want to delete the podcast \"%1$s\" and ALL its episodes (including downloaded episodes).</string>
@@ -179,11 +190,15 @@
<string name="removed_new_flag_label">Removed \"new\" flag</string>
<string name="mark_read_label">Mark as played</string>
<string name="marked_as_read_label">Marked as played</string>
+ <string name="mark_read_no_media_label">Mark as read</string>
+ <string name="marked_as_read_no_media_label">Marked as read</string>
+ <string name="play_this_to_seek_position">To jump to positions, you need to play the episode</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episode marked as played.</item>
<item quantity="other">%d episodes marked as played.</item>
</plurals>
<string name="mark_unread_label">Mark as unplayed</string>
+ <string name="mark_unread_label_no_media">Mark as unread</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episode marked as unplayed.</item>
<item quantity="other">%d episodes marked as unplayed.</item>
@@ -298,6 +313,7 @@
<string name="clear_queue_confirmation_msg">Please confirm that you want to clear the queue of ALL of the episodes in it</string>
<string name="sort_old_to_new">Old to new</string>
<string name="sort_new_to_old">New to old</string>
+ <string name="time_left_label">Time left:\u0020</string>
<!-- Variable Speed -->
<string name="download_plugin_label">Download Plugin</string>
@@ -331,9 +347,11 @@
<!-- Preferences -->
<string name="storage_pref">Storage</string>
+ <string name="storage_sum">Episode auto delete, Export, Import</string>
<string name="project_pref">Project</string>
<string name="queue_label">Queue</string>
<string name="integrations_label">Integrations</string>
+ <string name="integrations_sum">External services</string>
<string name="automation">Automation</string>
<string name="download_pref_details">Details</string>
<string name="import_export_pref">Import/Export</string>
@@ -365,9 +383,11 @@
<string name="pref_favorite_keeps_episodes_sum">Keep episodes when they are marked Favorite</string>
<string name="pref_favorite_keeps_episodes_title">Keep Favorite Episodes</string>
<string name="playback_pref">Playback</string>
+ <string name="playback_pref_sum">Headphone controls, Skip intervals, Queue</string>
<string name="network_pref">Network</string>
+ <string name="network_pref_sum">Update interval, Download controls, Mobile data</string>
<string name="pref_autoUpdateIntervallOrTime_title">Update Interval or Time of Day</string>
- <string name="pref_autoUpdateIntervallOrTime_sum">Specify an interval or a specific time of day to refresh the feeds automatically</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Specify an interval or a specific time of day to refresh the podcasts automatically</string>
<string name="pref_autoUpdateIntervallOrTime_message">You can set an <i>interval</i> like \"every 2 hours\", set a specific <i>time of day</i> like \"7:00 AM\" or <i>disable</i> automatic updates altogether.\n\n<small>Please note: Update times are inexact. You may encounter a short delay.</small></string>
<string name="pref_autoUpdateIntervallOrTime_Disable">Disable</string>
<string name="pref_autoUpdateIntervallOrTime_Interval">Set Interval</string>
@@ -382,12 +402,13 @@
<string name="pref_stream_over_download_sum">Display stream button instead of download button in lists.</string>
<string name="pref_mobileUpdate_title">Mobile Updates</string>
<string name="pref_mobileUpdate_sum">Select what should be allowed over the mobile data connection</string>
- <string name="pref_mobileUpdate_refresh">Feed refresh</string>
+ <string name="pref_mobileUpdate_refresh">Podcast refresh</string>
<string name="pref_mobileUpdate_images">Cover images</string>
<string name="pref_mobileUpdate_auto_download">Auto download</string>
<string name="pref_mobileUpdate_episode_download">Episode download</string>
<string name="pref_mobileUpdate_streaming">Streaming</string>
<string name="user_interface_label">User Interface</string>
+ <string name="user_interface_sum">Appearance, Subscription order, Lockscreen</string>
<string name="pref_set_theme_title">Select Theme</string>
<string name="pref_nav_drawer_items_title">Set Navigation Drawer items</string>
<string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer.</string>
@@ -434,7 +455,7 @@
<string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
- <string name="pref_feed_playback_speed_sum">The speed to use when starting audio playback for episodes in this feed</string>
+ <string name="pref_feed_playback_speed_sum">The speed to use when starting audio playback for episodes in this podcast</string>
<string name="pref_playback_time_respects_speed_title">Adjust media info to playback speed</string>
<string name="pref_playback_time_respects_speed_sum">Displayed position and duration are adapted to playback speed</string>
<string name="pref_fast_forward">Fast Forward Skip Time</string>
@@ -515,45 +536,45 @@
<string name="licenses_summary">AntennaPod uses other great software</string>
<!-- Search -->
- <string name="search_hint">Search for episodes</string>
- <string name="found_in_shownotes_label">Found in show notes</string>
- <string name="found_in_chapters_label">Found in chapters</string>
- <string name="found_in_authors_label">Found in author(s)</string>
- <string name="found_in_feeds_label">Found in podcast</string>
<string name="search_status_no_results">No results were found</string>
<string name="search_label">Search</string>
- <string name="found_in_title_label">Found in title</string>
<string name="no_results_for_query">No results were found for \"%1$s\"</string>
- <!-- OPML import and export -->
- <string name="opml_import_option">Option %1$d</string>
- <string name="opml_import_explanation_1">Choose a specific file path from the local filesystem.</string>
- <string name="opml_import_explanation_3">Many applications like Google Mail, Dropbox, Google Drive and most file managers can <i>open</i> OPML files <i>with</i> AntennaPod.</string>
+ <!-- import and export -->
+ <string name="import_export_summary">Move subscriptions and queue to another device</string>
+ <string name="database">Database</string>
+ <string name="opml">OPML</string>
+ <string name="html">HTML</string>
+ <string name="html_export_summary">Show your subscriptions to a friend</string>
+ <string name="opml_export_summary">Transfer your subscriptions to another podcast app</string>
+ <string name="opml_import_summary">Import your subscriptions from another podcast app</string>
+ <string name="database_export_summary">Transfer subscriptions, listened episodes and queue to AntennaPod on another device</string>
+ <string name="database_import_summary">Import AntennaPod database from another device</string>
<string name="opml_import_label">OPML Import</string>
- <string name="reading_opml_label">Reading OPML file</string>
<string name="opml_reader_error">An error has occurred while reading the OPML document:</string>
<string name="opml_import_error_no_file">No file selected!</string>
<string name="select_all_label">Select all</string>
<string name="deselect_all_label">Deselect all</string>
- <string name="choose_file_from_filesystem">From local filesystem</string>
<string name="opml_export_label">OPML export</string>
<string name="html_export_label">HTML export</string>
- <string name="exporting_label">Exporting&#8230;</string>
+ <string name="database_export_label">Database export</string>
+ <string name="database_import_label">Database import</string>
+ <string name="please_wait">Please wait&#8230;</string>
<string name="export_error_label">Export error</string>
<string name="export_success_title">Export successful</string>
<string name="export_success_sum">The exported file was written to:\n\n%1$s</string>
<string name="opml_import_ask_read_permission">Access to external storage is required to read the OPML file</string>
+ <string name="import_select_file">Select file to import</string>
+ <string name="import_ok">Import successful.\n\nPlease press OK to restart AntennaPod</string>
+ <string name="import_no_downgrade">This database was exported with a newer version of AntennaPod. Your current installation does not yet know how to handle this file.</string>
<!-- Sleep timer -->
<string name="set_sleeptimer_label">Set sleep timer</string>
<string name="disable_sleeptimer_label">Disable sleep timer</string>
- <string name="enter_time_here_label">Enter time</string>
<string name="sleep_timer_label">Sleep timer</string>
- <string name="time_left_label">Time left:\u0020</string>
<string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string>
- <string name="timer_about_to_expire_label"><b>When timer is about to expire:</b></string>
- <string name="shake_to_reset_label">Shake to reset timer</string>
- <string name="timer_vibration_label">Vibrate</string>
+ <string name="shake_to_reset_label">Shake to reset</string>
+ <string name="timer_vibration_label">Vibrate shortly before end</string>
<string name="time_seconds">seconds</string>
<string name="time_minutes">minutes</string>
<string name="time_hours">hours</string>
@@ -571,7 +592,6 @@
</plurals>
<string name="auto_enable_label">Auto-enable</string>
<string name="sleep_timer_enabled_label">Sleep timer enabled</string>
- <string name="sleep_timer_disabled_label">Sleep timer disabled</string>
<!-- gpodder.net -->
<string name="gpodnet_taglist_header">CATEGORIES</string>
@@ -657,7 +677,7 @@
<string name="episode_filters_exclude">Exclude</string>
<string name="episode_filters_hint">Single words \n\"Multiple Words\"</string>
<string name="keep_updated">Keep Updated</string>
- <string name="keep_updated_summary">Include this feed when (auto-)refreshing all feeds</string>
+ <string name="keep_updated_summary">Include this podcast when (auto-)refreshing all podcasts</string>
<string name="auto_download_disabled_globally">Auto download is disabled in the main AntennaPod settings</string>
<!-- Progress information -->
@@ -736,15 +756,6 @@
<!-- Subscriptions fragment -->
<string name="subscription_num_columns">Number of columns</string>
- <!-- Database import/export -->
- <string name="import_export">Database import/export</string>
- <string name="import_export_warning">This experimental function can be used to transfer your subscriptions and played episodes to another device.\n\nExported databases can only be imported when using the same version of AntennaPod. Otherwise, this function will lead to unexpected behavior.\n\nAfter importing, episodes might be displayed as downloaded even though they are not. Just press the play button of the episodes to make AntennaPod detect this.</string>
- <string name="label_import">Import</string>
- <string name="label_export">Export</string>
- <string name="import_select_file">Select file to import</string>
- <string name="export_ok">Export successful.</string>
- <string name="import_ok">Import successful.\n\nPlease press OK to restart AntennaPod</string>
-
<!-- Casting -->
<string name="cast_media_route_menu_title">Play on&#8230;</string>
<string name="cast_disconnect_label">Disconnect the cast session</string>
@@ -752,7 +763,6 @@
<string name="cast_failed_to_play">Failed to start the playback of media</string>
<string name="cast_failed_to_stop">Failed to stop the playback of media</string>
<string name="cast_failed_to_pause">Failed to pause the playback of media</string>
- <!--<string name="cast_failed_to_connect">Could not connect to the device</string>-->
<string name="cast_failed_setting_volume">Failed to set the volume</string>
<string name="cast_failed_no_connection">No connection to the cast device is present</string>
<string name="cast_failed_no_connection_trans">Connection to the cast device has been lost. Application is trying to re-establish the connection, if possible. Please wait for a few seconds and try again.</string>
@@ -770,7 +780,6 @@
<string name="notification_channel_playing_description">Allows to control playback. This is the main notification you see while playing a podcast.</string>
<string name="notification_channel_error">Errors</string>
<string name="notification_channel_error_description">Shown if something went wrong, for example if download or gpodder sync fails.</string>
- <string name="import_bad_file">Invalid/corrupt file</string>
<!-- Widget settings -->
<string name="widget_settings">Widget settings</string>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index a638917f4..5656ca0e9 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -76,6 +76,7 @@
<item name="ic_bookmark">@drawable/ic_bookmark_grey600_24dp</item>
<item name="batch_edit_fab_icon">@drawable/ic_fab_edit_white</item>
<item name="ic_key">@drawable/ic_key_grey600</item>
+ <item name="ic_volume_adaption">@drawable/ic_volume_adaption_grey</item>
<item name="master_switch_background">@color/master_switch_background_light</item>
<item name="currently_playing_background">@color/highlight_light</item>
@@ -164,6 +165,7 @@
<item name="ic_bookmark">@drawable/ic_bookmark_white_24dp</item>
<item name="batch_edit_fab_icon">@drawable/ic_fab_edit_white</item>
<item name="ic_key">@drawable/ic_key_white</item>
+ <item name="ic_volume_adaption">@drawable/ic_volume_adaption_white</item>
<item name="master_switch_background">@color/master_switch_background_dark</item>
<item name="currently_playing_background">@color/highlight_dark</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
@@ -266,7 +268,11 @@
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>
- <style name="Theme.AntennaPod.Dark.Splash" parent="Theme.AppCompat.NoActionBar">
+ <style name="Theme.AntennaPod.Splash" parent="Theme.Base.AntennaPod.Splash">
+ <!-- Room for API dependent attributes -->
+ </style>
+
+ <style name="Theme.Base.AntennaPod.Splash" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/bg_splash</item>
<item name="colorPrimary">@color/ic_launcher_background</item>
<item name="colorPrimaryDark">@color/ic_launcher_background</item>
@@ -279,6 +285,7 @@
<style name="AntennaPod.TextView.Heading" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_size_large</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:fontFamily">sans-serif-light</item>
</style>
<style name="AntennaPod.TextView.ListItemPrimaryTitle" parent="@android:style/TextAppearance.Small">