summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorH. Lehmann <ByteHamster@users.noreply.github.com>2018-06-08 20:40:57 +0200
committerGitHub <noreply@github.com>2018-06-08 20:40:57 +0200
commit11c3a45f6e9beb661c3d4fec491f10155c217568 (patch)
tree630269db11310c47891089a280caafc61478ef1f /core
parent719545fd3d54ed445d217e77b9a5005c6ba27c4f (diff)
parentf0151501a5195bf3c2dbd50336c51005e629196f (diff)
downloadAntennaPod-11c3a45f6e9beb661c3d4fec491f10155c217568.zip
Merge branch 'develop' into prevent-thrashing
Diffstat (limited to 'core')
-rw-r--r--core/build.gradle16
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/AntennaPodTestRunner.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java)2
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedImageMother.java9
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/service/download/DownloadServiceTest.java35
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java)24
-rw-r--r--core/src/main/AndroidManifest.xml11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java35
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java92
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java48
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java74
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java56
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java113
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java176
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java41
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java109
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java84
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java96
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java292
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java40
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java420
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java155
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java62
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java101
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java76
-rw-r--r--core/src/main/res/drawable/progress_bar_horizontal_trueblack.xml15
-rw-r--r--core/src/main/res/layout/player_widget.xml52
-rw-r--r--core/src/main/res/values-v21/styles.xml13
-rw-r--r--core/src/main/res/values/arrays.xml2
-rw-r--r--core/src/main/res/values/attrs.xml1
-rw-r--r--core/src/main/res/values/colors.xml2
-rw-r--r--core/src/main/res/values/strings.xml13
-rw-r--r--core/src/main/res/values/styles.xml56
-rw-r--r--core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java7
-rw-r--r--core/src/test/java/android/text/TextUtils.java32
-rw-r--r--core/src/test/java/android/util/Log.java245
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemMother.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemMother.java)4
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemTest.java)47
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/FeedMother.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedMother.java)6
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/FeedTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedTest.java)42
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/util/LongLongMapTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java)12
59 files changed, 1821 insertions, 1168 deletions
diff --git a/core/build.gradle b/core/build.gradle
index af1ac7ad0..eb857269a 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -10,7 +10,7 @@ android {
versionCode 1
versionName "1.0"
testApplicationId "de.danoeh.antennapod.core.tests"
- testInstrumentationRunner "de.danoeh.antennapod.core.tests.AntennaPodTestRunner"
+ testInstrumentationRunner "de.danoeh.antennapod.core.AntennaPodTestRunner"
}
buildTypes {
release {
@@ -49,6 +49,8 @@ repositories {
dependencies {
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
+ implementation "com.android.support:preference-v14:$supportVersion"
+ implementation "com.android.support:percent:$supportVersion"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "org.apache.commons:commons-text:$commonstextVersion"
implementation ("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") {
@@ -77,6 +79,18 @@ dependencies {
} else {
System.out.println("core: free build hack, skipping some dependencies")
}
+
+ testImplementation 'junit:junit:4.12'
+
+}
+
+tasks.withType(Test) {
+ testLogging {
+ exceptionFormat "full"
+ events "skipped", "passed", "failed"
+ showStandardStreams true
+ displayGranularity 2
+ }
}
allprojects {
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java b/core/src/androidTest/java/de/danoeh/antennapod/core/AntennaPodTestRunner.java
index 78e854b41..5d086c054 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/AntennaPodTestRunner.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.tests;
+package de.danoeh.antennapod.core;
import android.test.InstrumentationTestRunner;
import android.test.suitebuilder.TestSuiteBuilder;
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedImageMother.java b/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedImageMother.java
deleted file mode 100644
index 0fb4992ba..000000000
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedImageMother.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package de.danoeh.antennapod.core.feed;
-
-class FeedImageMother {
-
- public static FeedImage anyFeedImage() {
- return new FeedImage(0, "image", null, "http://example.com/picture", false);
- }
-
-}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/service/download/DownloadServiceTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/service/download/DownloadServiceTest.java
deleted file mode 100644
index 94cfb3278..000000000
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/service/download/DownloadServiceTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.danoeh.antennapod.core.tests.util.service.download;
-
-import android.test.AndroidTestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.service.download.DownloadService;
-
-public class DownloadServiceTest extends AndroidTestCase {
-
- public void testRemoveDuplicateImages() {
- List<FeedItem> items = new ArrayList<>();
- for (int i = 0; i < 50; i++) {
- FeedItem item = new FeedItem();
- String url = (i % 5 == 0) ? "dupe_url" : String.format("url_%d", i);
- item.setImage(new FeedImage(null, url, ""));
- items.add(item);
- }
- Feed feed = new Feed();
- feed.setItems(items);
-
- DownloadService.removeDuplicateImages(feed);
-
- assertEquals(50, items.size());
- for (int i = 0; i < items.size(); i++) {
- FeedItem item = items.get(i);
- String want = (i == 0) ? "dupe_url" : (i % 5 == 0) ? null : String.format("url_%d", i);
- assertEquals(want, item.getImageLocation());
- }
- }
-}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java
index ee90d9116..d5efdbc24 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.tests.util;
+package de.danoeh.antennapod.core.util;
import android.test.AndroidTestCase;
@@ -7,8 +7,14 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
-import de.danoeh.antennapod.core.util.DateUtils;
-
+/**
+ * Unit test for {@link DateUtils}.
+ *
+ * Note: It NEEDS to be run in android devices, i.e., it cannot be run in standard JDK, because
+ * the test invokes some android platform-specific behavior in the underlying
+ * {@link java.text.SimpleDateFormat} used by {@link DateUtils}.
+ *
+ */
public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithMicroseconds() throws Exception {
@@ -101,6 +107,12 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
+ /**
+ * Requires Android platform.
+ *
+ * Reason: Standard JDK cannot parse timezone <code>-08:00</code> (ISO 8601 format). It only accepts
+ * <code>-0800</code> (RFC 822 format)
+ */
public void testParseDateWithNoTimezonePadding() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2017, 1, 22, 22, 28, 0);
exp.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -109,6 +121,12 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
+ /**
+ * Requires Android platform. Root cause: {@link DateUtils} implementation makes
+ * use of ISO 8601 time zone, which does not work on standard JDK.
+ *
+ * @see #testParseDateWithNoTimezonePadding()
+ */
public void testParseDateWithForCest() throws Exception {
GregorianCalendar exp1 = new GregorianCalendar(2017, 0, 28, 22, 0, 0);
exp1.setTimeZone(TimeZone.getTimeZone("UTC"));
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 3257c13a7..1146f2a87 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -27,6 +27,7 @@
</service>
<service
android:name=".service.GpodnetSyncService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="true" />
<receiver
@@ -52,9 +53,17 @@
</intent-filter>
</receiver>
- <receiver android:name=".receiver.FeedUpdateReceiver">
+ <receiver android:name=".receiver.FeedUpdateReceiver"
+ android:label="@string/feed_update_receiver_name"
+ android:exported="true"> <!-- allow feeds update to be triggered by external apps -->
</receiver>
+ <service
+ android:name=".service.FeedUpdateJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+
+ </service>
+
</application>
</manifest>
diff --git a/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
index 96e3a77be..7f0fc4b2b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
@@ -14,7 +14,6 @@ import java.io.File;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
@@ -64,29 +63,6 @@ class UpdateManager {
}
private static void onUpgrade(final int oldVersionCode, final int newVersionCode) {
- if(oldVersionCode < 1030099) {
- // delete the now obsolete image cache
- // from now on, Glide will handle caching images
- new Thread() {
- public void run() {
- List<Feed> feeds = DBReader.getFeedList();
- for (Feed podcast : feeds) {
- List<FeedItem> episodes = DBReader.getFeedItemList(podcast);
- for (FeedItem episode : episodes) {
- FeedImage image = episode.getImage();
- if (image != null && image.isDownloaded() && image.getFile_url() != null) {
- File imageFile = new File(image.getFile_url());
- if (imageFile.exists()) {
- imageFile.delete();
- }
- image.setFile_url(null); // calls setDownloaded(false)
- DBWriter.setFeedImage(image);
- }
- }
- }
- }
- }.start();
- }
if(oldVersionCode < 1050004) {
if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
UserPreferences.enableSonic(true);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
index 5bd65f4e9..f4c99011a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
@@ -10,6 +10,7 @@ import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.shredzone.flattr4j.exception.FlattrException;
import java.util.LinkedList;
@@ -175,7 +176,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context), 0);
- Notification notification = new NotificationCompat.Builder(context)
+ Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
.setContentIntent(contentIntent)
.setContentTitle(context.getString(R.string.no_flattr_token_title))
@@ -208,7 +209,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
+ context.getString(R.string.flattr_click_failure_count, failed);
}
- Notification notification = new NotificationCompat.Builder(context)
+ Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
.setContentIntent(contentIntent)
.setContentTitle(title)
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java
new file mode 100644
index 000000000..b3241a8b6
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java
@@ -0,0 +1,13 @@
+package de.danoeh.antennapod.core.event;
+
+public class ServiceEvent {
+ public enum Action {
+ SERVICE_STARTED
+ }
+
+ public final Action action;
+
+ public ServiceEvent(Action action) {
+ this.action = action;
+ }
+}
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 78df74ee7..3395653f3 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
@@ -44,7 +44,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* Name of the author
*/
private String author;
- private FeedImage image;
+ private String imageUrl;
private List<FeedItem> items;
/**
@@ -96,7 +96,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* This constructor is used for restoring a feed from the database.
*/
public Feed(long id, String lastUpdate, String title, String customTitle, String link, String description, String paymentLink,
- String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
+ String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
@@ -111,7 +111,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.language = language;
this.type = type;
this.feedIdentifier = feedIdentifier;
- this.image = image;
+ this.imageUrl = imageUrl;
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
@@ -128,9 +128,9 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* This constructor is used for test purposes and uses a default flattr status object.
*/
public Feed(long id, String lastUpdate, String title, String link, String description, String paymentLink,
- String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
+ String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded) {
- this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, image,
+ this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, imageUrl,
fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
}
@@ -191,6 +191,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
+ int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
Feed feed = new Feed(
cursor.getLong(indexId),
@@ -204,7 +205,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
cursor.getString(indexLanguage),
cursor.getString(indexType),
cursor.getString(indexFeedIdentifier),
- null,
+ cursor.getString(indexImageUrl),
cursor.getString(indexFileUrl),
cursor.getString(indexDownloadUrl),
cursor.getInt(indexDownloaded) > 0,
@@ -266,8 +267,8 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
public void updateFromOther(Feed other) {
// don't update feed's download_url, we do that manually if redirected
// see AntennapodHttpClient
- if (other.image != null) {
- this.image = other.image;
+ if (other.imageUrl != null) {
+ this.imageUrl = other.imageUrl;
}
if (other.feedTitle != null) {
feedTitle = other.feedTitle;
@@ -305,8 +306,8 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
if (super.compareWithOther(other)) {
return true;
}
- if (other.image != null) {
- if (image == null || !TextUtils.equals(image.download_url, other.image.download_url)) {
+ if (other.imageUrl != null) {
+ if (imageUrl == null || !TextUtils.equals(imageUrl, other.imageUrl)) {
return true;
}
}
@@ -411,12 +412,12 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.description = description;
}
- public FeedImage getImage() {
- return image;
+ public String getImageUrl() {
+ return imageUrl;
}
- public void setImage(FeedImage image) {
- this.image = image;
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
}
public List<FeedItem> getItems() {
@@ -505,11 +506,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
@Override
public String getImageLocation() {
- if (image != null) {
- return image.getImageLocation();
- } else {
- return null;
- }
+ return imageUrl;
}
public int getPageNr() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java
deleted file mode 100644
index 45bd2ad31..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package de.danoeh.antennapod.core.feed;
-
-import android.database.Cursor;
-
-import java.io.File;
-
-import de.danoeh.antennapod.core.asynctask.ImageResource;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-
-
-public class FeedImage extends FeedFile implements ImageResource {
- public static final int FEEDFILETYPE_FEEDIMAGE = 1;
-
- private String title;
- private FeedComponent owner;
-
- public FeedImage(FeedComponent owner, String download_url, String title) {
- super(null, download_url, false);
- this.download_url = download_url;
- this.title = title;
- this.owner = owner;
- }
-
- public FeedImage(long id, String title, String file_url,
- String download_url, boolean downloaded) {
- super(file_url, download_url, downloaded);
- this.id = id;
- this.title = title;
- }
-
- public FeedImage() {
- super();
- }
-
- public static FeedImage fromCursor(Cursor cursor) {
- int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
- int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
- int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
- int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
- int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
-
- return new FeedImage(
- cursor.getLong(indexId),
- cursor.getString(indexTitle),
- cursor.getString(indexFileUrl),
- cursor.getString(indexDownloadUrl),
- cursor.getInt(indexDownloaded) > 0
- );
- }
-
-
- @Override
- public String getHumanReadableIdentifier() {
- if (owner != null && owner.getHumanReadableIdentifier() != null) {
- return owner.getHumanReadableIdentifier();
- } else {
- return download_url;
- }
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEEDIMAGE;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public FeedComponent getOwner() {
- return owner;
- }
-
- public void setOwner(FeedComponent owner) {
- this.owner = owner;
- }
-
- @Override
- public String getImageLocation() {
- if (file_url != null && downloaded) {
- return new File(file_url).getAbsolutePath();
- } else if(download_url != null) {
- return download_url;
- } else {
- return null;
- }
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index 87298d4c3..b0a87c885 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -4,6 +4,7 @@ import android.database.Cursor;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -14,7 +15,6 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -75,7 +75,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* in the database. The 'hasChapters' attribute should be used to check if this item has any chapters.
* */
private List<Chapter> chapters;
- private FeedImage image;
+ private String imageUrl;
/*
* 0: auto download disabled
@@ -100,7 +100,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* This constructor is used by DBReader.
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
- FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, int state,
+ FlattrStatus flattrStatus, boolean hasChapters, String imageUrl, int state,
String itemIdentifier, long autoDownload) {
this.id = id;
this.title = title;
@@ -110,7 +110,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.feedId = feedId;
this.flattrStatus = flattrStatus;
this.hasChapters = hasChapters;
- this.image = image;
+ this.imageUrl = imageUrl;
this.state = state;
this.itemIdentifier = itemIdentifier;
this.autoDownload = autoDownload;
@@ -158,6 +158,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
int indexRead = cursor.getColumnIndex(PodDBAdapter.KEY_READ);
int indexItemIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_ITEM_IDENTIFIER);
int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
+ int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
long id = cursor.getInt(indexId);
String title = cursor.getString(indexTitle);
@@ -170,15 +171,16 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
int state = cursor.getInt(indexRead);
String itemIdentifier = cursor.getString(indexItemIdentifier);
long autoDownload = cursor.getLong(indexAutoDownload);
+ String imageUrl = cursor.getString(indexImageUrl);
return new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
- hasChapters, null, state, itemIdentifier, autoDownload);
+ hasChapters, imageUrl, state, itemIdentifier, autoDownload);
}
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
- if (other.image != null) {
- this.image = other.image;
+ if (other.imageUrl != null) {
+ this.imageUrl = other.imageUrl;
}
if (other.title != null) {
title = other.title;
@@ -212,9 +214,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
chapters = other.chapters;
}
}
- if (image == null) {
- image = other.image;
- }
}
/**
@@ -389,8 +388,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public String getImageLocation() {
if(media != null && media.hasEmbeddedPicture()) {
return media.getImageLocation();
- } else if (image != null) {
- return image.getImageLocation();
+ } else if (imageUrl != null) {
+ return imageUrl;
} else if (feed != null) {
return feed.getImageLocation();
} else {
@@ -426,29 +425,12 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* Returns the image of this item or the image of the feed if this item does
* not have its own image.
*/
- public FeedImage getImage() {
- return (hasItemImage()) ? image : feed.getImage();
- }
-
- public void setImage(FeedImage image) {
- this.image = image;
- if (image != null) {
- image.setOwner(this);
- }
- }
-
- /**
- * Returns true if this FeedItem has its own image, false otherwise.
- */
- public boolean hasItemImage() {
- return image != null;
+ public String getImageUrl() {
+ return (imageUrl != null) ? imageUrl : feed.getImageUrl();
}
- /**
- * Returns true if this FeedItem has its own image and the image has been downloaded.
- */
- public boolean hasItemImageDownloaded() {
- return image != null && image.isDownloaded();
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 2d551e1b2..a22422596 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -554,15 +554,9 @@ public class FeedMedia extends FeedFile implements Playable {
public Callable<String> loadShownotes() {
return () -> {
if (item == null) {
- item = DBReader.getFeedItem(
- itemID);
+ item = DBReader.getFeedItem(itemID);
}
- if (item.getContentEncoded() == null || item.getDescription() == null) {
- DBReader.loadExtraInformationOfFeedItem(
- item);
-
- }
- return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
+ return item.loadShownotes().call();
};
}
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 a93012d59..e59fe4c11 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
@@ -1,18 +1,21 @@
package de.danoeh.antennapod.core.preferences;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
-
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.service.download.ProxyConfig;
+import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import org.json.JSONArray;
import org.json.JSONException;
@@ -21,19 +24,9 @@ import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
-import de.danoeh.antennapod.core.service.download.ProxyConfig;
-import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
-import de.danoeh.antennapod.core.util.Converter;
-
/**
* Provides access to preferences set by the user in the settings screen. A
* private instance of this class must first be instantiated via
@@ -168,6 +161,8 @@ public class UserPreferences {
int theme = getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.style.Theme_AntennaPod_Dark_NoTitle;
+ } else if (theme == R.style.Theme_AntennaPod_TrueBlack) {
+ return R.style.Theme_AntennaPod_TrueBlack_NoTitle;
} else {
return R.style.Theme_AntennaPod_Light_NoTitle;
}
@@ -604,6 +599,8 @@ public class UserPreferences {
return R.style.Theme_AntennaPod_Light;
case 1:
return R.style.Theme_AntennaPod_Dark;
+ case 2:
+ return R.style.Theme_AntennaPod_TrueBlack;
default:
return R.style.Theme_AntennaPod_Light;
}
@@ -781,58 +778,15 @@ public class UserPreferences {
int[] timeOfDay = getUpdateTimeOfDay();
Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
if (timeOfDay.length == 2) {
- restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]);
+ AutoUpdateManager.restartUpdateTimeOfDayAlarm(context, timeOfDay[0], timeOfDay[1]);
} else {
long milliseconds = getUpdateInterval();
long startTrigger = milliseconds;
if (now) {
startTrigger = TimeUnit.SECONDS.toMillis(10);
}
- restartUpdateIntervalAlarm(startTrigger, milliseconds);
- }
- }
-
- /**
- * Sets the interval in which the feeds are refreshed automatically
- */
- private static void restartUpdateIntervalAlarm(long triggerAtMillis, long intervalMillis) {
- Log.d(TAG, "Restarting update alarm.");
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(context, FeedUpdateReceiver.class);
- PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
- alarmManager.cancel(updateIntent);
- if (intervalMillis > 0) {
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + triggerAtMillis,
- updateIntent);
- Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
- } else {
- Log.d(TAG, "Automatic update was deactivated");
- }
- }
-
- /**
- * Sets time of day the feeds are refreshed automatically
- */
- private static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) {
- Log.d(TAG, "Restarting update alarm.");
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
- new Intent(context, FeedUpdateReceiver.class), 0);
- alarmManager.cancel(updateIntent);
-
- Calendar now = Calendar.getInstance();
- Calendar alarm = (Calendar)now.clone();
- alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
- alarm.set(Calendar.MINUTE, minute);
- if (alarm.before(now) || alarm.equals(now)) {
- alarm.add(Calendar.DATE, 1);
+ AutoUpdateManager.restartUpdateIntervalAlarm(context, startTrigger, milliseconds);
}
- Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
- alarmManager.set(AlarmManager.RTC_WAKEUP,
- alarm.getTimeInMillis(),
- updateIntent);
- Log.d(TAG, "Changed alarm to new time of day " + hoursOfDay + ":" + minute);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index 9bbeb7c88..05e12f6df 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -7,8 +7,7 @@ import android.util.Log;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.FeedUpdateUtils;
/**
* Refreshes all feeds when it receives an intent
@@ -21,11 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent");
ClientConfig.initialize(context);
- if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
- DBTasks.refreshAllFeeds(context, null);
- } else {
- Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
- }
+ FeedUpdateUtils.startAutoUpdate(context, null);
UserPreferences.restartUpdateAlarm(false);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
index 9b4b91151..b191dbf8b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.KeyEvent;
@@ -29,7 +30,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
Intent serviceIntent = new Intent(context, PlaybackService.class);
serviceIntent.putExtra(EXTRA_KEYCODE, event.getKeyCode());
serviceIntent.putExtra(EXTRA_SOURCE, event.getSource());
- context.startService(serviceIntent);
+ ContextCompat.startForegroundService(context, serviceIntent);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
new file mode 100644
index 000000000..edc2ea3e0
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
@@ -0,0 +1,56 @@
+package de.danoeh.antennapod.core.receiver;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
+
+import java.util.Arrays;
+
+
+public class PlayerWidget extends AppWidgetProvider {
+ private static final String TAG = "PlayerWidget";
+ private static final String PREFS_NAME = "PlayerWidgetPrefs";
+ private static final String KEY_ENABLED = "WidgetEnabled";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive");
+ super.onReceive(context, intent);
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ super.onEnabled(context);
+ Log.d(TAG, "Widget enabled");
+ setEnabled(context, true);
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]");
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ super.onDisabled(context);
+ Log.d(TAG, "Widget disabled");
+ setEnabled(context, false);
+ }
+
+ public static boolean isEnabled(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_ENABLED, false);
+ }
+
+ private void setEnabled(Context context, boolean enabled) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(KEY_ENABLED, enabled).apply();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
new file mode 100644
index 000000000..55a8d6b86
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
@@ -0,0 +1,34 @@
+package de.danoeh.antennapod.core.service;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.FeedUpdateUtils;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class FeedUpdateJobService extends JobService {
+ private static final String TAG = "FeedUpdateJobService";
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.d(TAG, "Job started");
+ ClientConfig.initialize(getApplicationContext());
+
+ FeedUpdateUtils.startAutoUpdate(getApplicationContext(), () -> {
+ UserPreferences.restartUpdateAlarm(false);
+ jobFinished(params, false); // needsReschedule = false
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return true;
+ }
+
+}
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 a723097a2..de040603d 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
@@ -3,10 +3,10 @@ package de.danoeh.antennapod.core.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.v4.app.JobIntentService;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.ArrayMap;
import android.util.Log;
@@ -15,6 +15,7 @@ import android.util.Pair;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
@@ -37,12 +38,13 @@ 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.gui.NotificationUtils;
/**
* Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument.
* This class also provides static methods for starting the GpodnetSyncService.
*/
-public class GpodnetSyncService extends Service {
+public class GpodnetSyncService extends JobIntentService {
private static final String TAG = "GpodnetSyncService";
private static final long WAIT_INTERVAL = 5000L;
@@ -55,12 +57,17 @@ public class GpodnetSyncService extends Service {
private GpodnetService service;
- private boolean syncSubscriptions = false;
- private boolean syncActions = false;
+ private static final AtomicInteger syncActionCount = new AtomicInteger(0);
+ private static boolean syncSubscriptions = false;
+ private static boolean syncActions = false;
+
+ private static void enqueueWork(Context context, Intent intent) {
+ enqueueWork(context, GpodnetSyncService.class, 0, intent);
+ }
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
+ protected void onHandleWork(@NonNull Intent intent) {
+ final String action = intent.getStringExtra(ARG_ACTION);
if (action != null) {
switch(action) {
case ACTION_SYNC:
@@ -78,24 +85,20 @@ public class GpodnetSyncService extends Service {
}
if(syncSubscriptions || syncActions) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
- syncWaiterThread.restart();
+ int syncActionId = syncActionCount.incrementAndGet();
+ try {
+ Thread.sleep(WAIT_INTERVAL);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if (syncActionId == syncActionCount.get()) {
+ // onHandleWork was not called again in the meantime
+ sync();
+ }
}
} else {
Log.e(TAG, "Received invalid intent: action argument is null");
}
- return START_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.d(TAG, "onDestroy");
- syncWaiterThread.interrupt();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
}
private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
@@ -109,6 +112,7 @@ public class GpodnetSyncService extends Service {
private synchronized void sync() {
if (!GpodnetPreferences.loggedIn() || !NetworkUtils.networkAvailable()) {
+ stopForeground(true);
stopSelf();
return;
}
@@ -125,7 +129,6 @@ public class GpodnetSyncService extends Service {
}
syncActions = false;
}
- stopSelf();
}
private synchronized void syncSubscriptionChanges() {
@@ -319,7 +322,7 @@ public class GpodnetSyncService extends Service {
}
PendingIntent activityIntent = ClientConfig.gpodnetCallbacks.getGpodnetSyncServiceErrorNotificationPendingIntent(this);
- Notification notification = new NotificationCompat.Builder(this)
+ Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setContentTitle(title)
.setContentText(description)
.setContentIntent(activityIntent)
@@ -331,69 +334,11 @@ public class GpodnetSyncService extends Service {
nm.notify(id, notification);
}
- private final WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
- @Override
- public void onWaitCompleted() {
- sync();
- }
- };
-
- private abstract class WaiterThread {
- private final long waitInterval;
- private Thread thread;
-
- private WaiterThread(long waitInterval) {
- this.waitInterval = waitInterval;
- reinit();
- }
-
- public abstract void onWaitCompleted();
-
- public void exec() {
- if (!thread.isAlive()) {
- thread.start();
- }
- }
-
- private void reinit() {
- if (thread != null && thread.isAlive()) {
- Log.d(TAG, "Interrupting waiter thread");
- thread.interrupt();
- }
- thread = new Thread() {
- @Override
- public void run() {
- try {
- Thread.sleep(waitInterval);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (!isInterrupted()) {
- synchronized (this) {
- onWaitCompleted();
- }
- }
- }
- };
- }
-
- public void restart() {
- reinit();
- exec();
- }
-
- public void interrupt() {
- if (thread != null && thread.isAlive()) {
- thread.interrupt();
- }
- }
- }
-
public static void sendSyncIntent(Context context) {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
@@ -401,7 +346,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
@@ -409,7 +354,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
}
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
new file mode 100644
index 000000000..2fd790ac7
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
@@ -0,0 +1,176 @@
+package de.danoeh.antennapod.core.service;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.v4.app.JobIntentService;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.RemoteViews;
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.service.playback.PlayerStatus;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.receiver.PlayerWidget;
+
+/**
+ * Updates the state of the player widget
+ */
+public class PlayerWidgetJobService extends JobIntentService {
+ private static final String TAG = "PlayerWidgetJobService";
+
+ private PlaybackService playbackService;
+ private final Object waitForService = new Object();
+
+ public static void updateWidget(Context context) {
+ enqueueWork(context, PlayerWidgetJobService.class, 0, new Intent(context, PlayerWidgetJobService.class));
+ }
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ if (!PlayerWidget.isEnabled(getApplicationContext())) {
+ return;
+ }
+
+ if (PlaybackService.isRunning && playbackService == null) {
+ synchronized (waitForService) {
+ bindService(new Intent(this, PlaybackService.class), mConnection, 0);
+ while (playbackService == null) {
+ try {
+ waitForService.wait();
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }
+ }
+
+ updateViews();
+
+ if (playbackService != null) {
+ try {
+ unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "IllegalArgumentException when trying to unbind service");
+ }
+ }
+ }
+
+ private void updateViews() {
+
+ ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
+ AppWidgetManager manager = AppWidgetManager.getInstance(this);
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.player_widget);
+ PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this), 0);
+
+ final PendingIntent startAppPending = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ boolean nothingPlaying = false;
+ Playable media;
+ PlayerStatus status;
+ if (playbackService != null) {
+ media = playbackService.getPlayable();
+ status = playbackService.getStatus();
+ } else {
+ media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
+ status = PlayerStatus.STOPPED;
+ }
+
+ if (media != null) {
+ views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
+
+ views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
+
+ String progressString;
+ if (playbackService != null) {
+ progressString = getProgressString(playbackService.getCurrentPosition(), playbackService.getDuration());
+ } else {
+ progressString = getProgressString(media.getPosition(), media.getDuration());
+ }
+
+ if (progressString != null) {
+ views.setViewVisibility(R.id.txtvProgress, View.VISIBLE);
+ views.setTextViewText(R.id.txtvProgress, progressString);
+ }
+
+ 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));
+ }
+ } 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.setOnClickPendingIntent(R.id.butPlay, createMediaButtonIntent());
+ } else {
+ nothingPlaying = true;
+ }
+
+ if (nothingPlaying) {
+ // start the app if they click anything
+ views.setOnClickPendingIntent(R.id.layout_left, startAppPending);
+ views.setOnClickPendingIntent(R.id.butPlay, startAppPending);
+ views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
+ views.setTextViewText(R.id.txtvTitle,
+ this.getString(R.string.no_media_playing_label));
+ views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
+ }
+
+ manager.updateAppWidget(playerWidget, views);
+ }
+
+ /**
+ * Creates an intent which fakes a mediabutton press
+ */
+ private PendingIntent createMediaButtonIntent() {
+ KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ Intent startingIntent = new Intent(getBaseContext(), MediaButtonReceiver.class);
+ startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
+ startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+
+ return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
+ }
+
+ private String getProgressString(int position, int duration) {
+ if (position > 0 && duration > 0) {
+ return Converter.getDurationStringLong(position) + " / "
+ + Converter.getDurationStringLong(duration);
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Connection to service established");
+ if (service instanceof PlaybackService.LocalBinder) {
+ synchronized (waitForService) {
+ playbackService = ((PlaybackService.LocalBinder) service).getService();
+ waitForService.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ playbackService = null;
+ Log.d(TAG, "Disconnected from service");
+ }
+
+ };
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index 57c1964f1..4bd2d8f19 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -7,8 +7,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.os.Binder;
import android.os.Build;
@@ -23,6 +21,7 @@ import android.util.Pair;
import android.webkit.URLUtil;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.io.FileUtils;
import org.xml.sax.SAXException;
@@ -57,7 +56,6 @@ import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -297,6 +295,7 @@ public class DownloadService extends Service {
setupNotificationBuilders();
requester = DownloadRequester.getInstance();
+ startForeground(NOTIFICATION_ID, updateNotifications());
}
@Override
@@ -342,7 +341,7 @@ public class DownloadService extends Service {
}
private void setupNotificationBuilders() {
- notificationCompatBuilder = new NotificationCompat.Builder(this)
+ notificationCompatBuilder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOADING)
.setOngoing(true)
.setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
.setSmallIcon(R.drawable.stat_notify_sync);
@@ -355,7 +354,7 @@ public class DownloadService extends Service {
/**
* Updates the contents of the service's notifications. Should be called
- * before setupNotificationBuilders.
+ * after setupNotificationBuilders.
*/
private Notification updateNotifications() {
if (notificationCompatBuilder == null) {
@@ -492,9 +491,7 @@ public class DownloadService extends Service {
if (status.isSuccessful()) {
successfulDownloads++;
} else if (!status.isCancelled()) {
- if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
- createReport = true;
- }
+ createReport = true;
failedDownloads++;
}
}
@@ -502,7 +499,7 @@ public class DownloadService extends Service {
if (createReport) {
Log.d(TAG, "Creating report");
// create notification object
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setTicker(getString(R.string.download_report_title))
.setContentTitle(getString(R.string.download_report_content_title))
.setContentText(
@@ -554,7 +551,7 @@ public class DownloadService extends Service {
final String resourceTitle = (downloadRequest.getTitle() != null) ?
downloadRequest.getTitle() : downloadRequest.getSource();
- NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this, NotificationUtils.CHANNEL_ID_USER_ACTION);
builder.setTicker(getText(R.string.authentication_notification_title))
.setContentTitle(getText(R.string.authentication_notification_title))
.setContentText(getText(R.string.authentication_notification_msg))
@@ -691,10 +688,6 @@ public class DownloadService extends Service {
Log.d(TAG, "Bundling " + results.size() + " feeds");
- for (Pair<DownloadRequest, FeedHandlerResult> result : results) {
- removeDuplicateImages(result.second.feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet.
- }
-
// Save information of feed in DB
if (dbUpdateFuture != null) {
try {
@@ -1104,26 +1097,6 @@ public class DownloadService extends Service {
}
}
- /**
- * Checks if the FeedItems of this feed have images that point to the same URL. If two FeedItems
- * have an image that points to the same URL, the reference of the second item is removed, so
- * that every image reference is unique.
- */
- @VisibleForTesting
- public static void removeDuplicateImages(Feed feed) {
- Set<String> known = new HashSet<>();
- for (FeedItem item : feed.getItems()) {
- String url = item.hasItemImage() ? item.getImage().getDownload_url() : null;
- if (url != null) {
- if (known.contains(url)) {
- item.setImage(null);
- } else {
- known.add(url);
- }
- }
- }
- }
-
private static String compileNotificationString(List<Downloader> downloads) {
List<String> lines = new ArrayList<>(downloads.size());
for (Downloader downloader : downloads) {
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 7ab0931d6..8cce02155 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
@@ -20,7 +20,6 @@ import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
@@ -50,13 +49,8 @@ public class HttpDownloader extends Downloader {
if (request.isDeleteOnFailure() && fileExists) {
Log.w(TAG, "File already exists");
- if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
- onFail(DownloadError.ERROR_FILE_EXISTS, null);
- return;
- } else {
- onSuccess();
- return;
- }
+ onSuccess();
+ return;
}
OkHttpClient.Builder httpClientBuilder = AntennapodHttpClient.newBuilder();
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 af735aefd..0e64f484f 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
@@ -609,6 +609,9 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
public void shutdown() {
executor.shutdown();
if (mediaPlayer != null) {
+ try {
+ mediaPlayer.stop();
+ } catch (Exception ignore) { }
mediaPlayer.release();
}
releaseWifiLockIfNecessary();
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 67a2cdad2..be6cb346d 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
@@ -30,12 +30,10 @@ import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.view.InputDeviceCompat;
-import android.support.v7.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.widget.Toast;
@@ -49,6 +47,7 @@ import java.util.List;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.MessageEvent;
+import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -60,11 +59,13 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.IntList;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -74,8 +75,6 @@ import de.greenrobot.event.EventBus;
* Controls the MediaPlayer that plays a FeedMedia-file
*/
public class PlaybackService extends MediaBrowserServiceCompat {
- public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE";
- public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE";
/**
* Logging tag
*/
@@ -313,6 +312,31 @@ public class PlaybackService extends MediaBrowserServiceCompat {
flavorHelper.initializeMediaPlayer(PlaybackService.this);
mediaSession.setActive(true);
+
+ NotificationCompat.Builder notificationBuilder = createBasicNotification();
+ startForeground(NOTIFICATION_ID, notificationBuilder.build());
+ EventBus.getDefault().post(new ServiceEvent(ServiceEvent.Action.SERVICE_STARTED));
+
+
+ setupNotification(Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext()));
+ }
+
+ private NotificationCompat.Builder createBasicNotification() {
+ final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
+
+ final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return new NotificationCompat.Builder(
+ this, NotificationUtils.CHANNEL_ID_PLAYING)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText("Service is running") // Just in case the notification is not updated (should not occur)
+ .setOngoing(false)
+ .setContentIntent(pIntent)
+ .setWhen(0) // we don't need the time
+ .setSmallIcon(smallIcon)
+ .setPriority(NotificationCompat.PRIORITY_MIN);
}
@Override
@@ -447,8 +471,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (keycode != -1) {
Log.d(TAG, "Received media button event");
- handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE,
- InputDeviceCompat.SOURCE_CLASS_NONE));
+ handleKeycode(keycode, true);
} else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
started = true;
boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
@@ -472,7 +495,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* Handles media button events
* return: keycode was handled
*/
- private boolean handleKeycode(int keycode, int source) {
+ private boolean handleKeycode(int keycode, boolean notificationButton) {
Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
@@ -488,6 +511,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
+ } else if (mediaPlayer.getPlayable() == null) {
+ startPlayingFromPreferences();
}
return true;
case KeyEvent.KEYCODE_MEDIA_PLAY:
@@ -496,6 +521,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
+ } else if (mediaPlayer.getPlayable() == null) {
+ startPlayingFromPreferences();
}
return true;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
@@ -505,7 +532,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
- if (source == InputDevice.SOURCE_CLASS_NONE ||
+ if (notificationButton ||
UserPreferences.shouldHardwareButtonSkip()) {
// assume the skip command comes from a notification or the lockscreen
// a >| skip button should actually skip
@@ -549,6 +576,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return false;
}
+ private void startPlayingFromPreferences() {
+ Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
+ if (playable != null) {
+ mediaPlayer.playMediaObject(playable, false, true, true);
+ started = true;
+ PlaybackService.this.updateMediaSessionMetadata(playable);
+ }
+ }
+
/**
* Called by a mediaplayer Activity as soon as it has prepared its
* mediaplayer.
@@ -567,8 +603,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void notifyVideoSurfaceAbandoned() {
- stopForeground(!UserPreferences.isPersistNotify());
+ mediaPlayer.pause(true, false);
mediaPlayer.resetVideoSurface();
+ setupNotification(getPlayable());
+ stopForeground(!UserPreferences.isPersistNotify());
}
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@@ -602,7 +640,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onWidgetUpdaterTick() {
- updateWidget();
+ PlayerWidgetJobService.updateWidget(getBaseContext());
}
@Override
@@ -664,7 +702,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED);
// statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
sendBroadcast(statusUpdate);
- updateWidget();
+ PlayerWidgetJobService.updateWidget(getBaseContext());
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
}
@@ -807,7 +845,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (!isCasting) {
stopForeground(true);
}
- stopWidgetUpdater();
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
@@ -1171,10 +1208,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* Prepares notification and starts the service in the foreground.
*/
private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
- final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
- PlaybackService.getPlayerActivityIntent(this),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ setupNotification(info.playable);
+ }
+ private synchronized void setupNotification(final Playable playable) {
if (notificationSetupThread != null) {
notificationSetupThread.interrupt();
}
@@ -1184,12 +1221,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void run() {
Log.d(TAG, "Starting background work");
- if (info.playable != null) {
+ if (playable != null) {
int iconSize = getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_width);
try {
icon = Glide.with(PlaybackService.this)
- .load(info.playable.getImageLocation())
+ .load(playable.getImageLocation())
.asBitmap()
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.centerCrop()
@@ -1208,24 +1245,18 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return;
}
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
- final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
- if (!Thread.currentThread().isInterrupted() && started && info.playable != null) {
- String contentText = info.playable.getEpisodeTitle();
- String contentTitle = info.playable.getFeedTitle();
+ if (!Thread.currentThread().isInterrupted() && started && playable != null) {
+ String contentText = playable.getEpisodeTitle();
+ String contentTitle = playable.getFeedTitle();
Notification notification;
// Builder is v7, even if some not overwritten methods return its parent's v4 interface
- NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(
- PlaybackService.this)
- .setContentTitle(contentTitle)
+ NotificationCompat.Builder notificationBuilder = createBasicNotification();
+ notificationBuilder.setContentTitle(contentTitle)
.setContentText(contentText)
- .setOngoing(false)
- .setContentIntent(pIntent)
- .setLargeIcon(icon)
- .setSmallIcon(smallIcon)
- .setWhen(0) // we don't need the time
- .setPriority(UserPreferences.getNotifyPriority()); // set notification priority
+ .setPriority(UserPreferences.getNotifyPriority())
+ .setLargeIcon(icon); // set notification priority
IntList compactActionList = new IntList();
int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
@@ -1293,7 +1324,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_STOP, numActions);
- notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
+ notificationBuilder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(compactActionList.toArray())
.setShowCancelButton(true)
@@ -1360,16 +1391,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
}
- private void stopWidgetUpdater() {
- taskManager.cancelWidgetUpdater();
- sendBroadcast(new Intent(STOP_WIDGET_UPDATE));
- }
-
- private void updateWidget() {
- PlaybackService.this.sendBroadcast(new Intent(
- FORCE_WIDGET_UPDATE));
- }
-
public boolean sleepTimerActive() {
return taskManager.isSleepTimerActive();
}
@@ -1762,11 +1783,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public boolean onMediaButtonEvent(final Intent mediaButton) {
Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
if (mediaButton != null) {
- KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ KeyEvent keyEvent = (KeyEvent) mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent != null &&
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
keyEvent.getRepeatCount() == 0) {
- return handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource());
+ return handleKeycode(keyEvent.getKeyCode(), false);
}
}
return false;
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 fa87cc216..5eb5145e8 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
@@ -13,7 +13,6 @@ import java.util.Map;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -201,25 +200,15 @@ public final class DBReader {
private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter, Cursor cursor) {
List<FeedItem> result = new ArrayList<>(cursor.getCount());
- LongList imageIds = new LongList(cursor.getCount());
LongList itemIds = new LongList(cursor.getCount());
if (cursor.moveToFirst()) {
do {
- int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
- long imageId = cursor.getLong(indexImage);
- imageIds.add(imageId);
-
FeedItem item = FeedItem.fromCursor(cursor);
result.add(item);
itemIds.add(item.getId());
} while (cursor.moveToNext());
- Map<Long, FeedImage> images = getFeedImages(adapter, imageIds.toArray());
Map<Long, FeedMedia> medias = getFeedMedia(adapter, itemIds);
- for (int i = 0; i < result.size(); i++) {
- FeedItem item = result.get(i);
- long imageId = imageIds.get(i);
- FeedImage image = images.get(imageId);
- item.setImage(image);
+ for (FeedItem item : result) {
FeedMedia media = medias.get(item.getId());
item.setMedia(media);
if (media != null) {
@@ -254,24 +243,9 @@ public final class DBReader {
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, Cursor cursor) {
- final FeedImage image;
- int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
- long imageId = cursor.getLong(indexImage);
- if (imageId != 0) {
- image = getFeedImage(adapter, imageId);
- } else {
- image = null;
- }
-
Feed feed = Feed.fromCursor(cursor);
- if (image != null) {
- feed.setImage(image);
- image.setOwner(feed);
- }
-
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
-
return feed;
}
@@ -839,62 +813,6 @@ public final class DBReader {
}
/**
- * Searches the DB for a FeedImage of the given id.
- *
- * @param imageId The id of the object
- * @return The found object
- */
- public static FeedImage getFeedImage(final long imageId) {
- Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]");
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- try {
- return getFeedImage(adapter, imageId);
- } finally {
- adapter.close();
- }
- }
-
- /**
- * Searches the DB for a FeedImage of the given id.
- *
- * @param imageId The id of the object
- * @return The found object
- */
- private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) {
- return getFeedImages(adapter, imageId).get(imageId);
- }
-
- /**
- * Searches the DB for a FeedImage of the given id.
- *
- * @param imageIds The ids of the images
- * @return Map that associates the id of an image with the image itself
- */
- private static Map<Long, FeedImage> getFeedImages(PodDBAdapter adapter, final long... imageIds) {
- String[] ids = new String[imageIds.length];
- for (int i = 0, len = imageIds.length; i < len; i++) {
- ids[i] = String.valueOf(imageIds[i]);
- }
- Cursor cursor = adapter.getImageCursor(ids);
- int imageCount = cursor.getCount();
- if (imageCount == 0) {
- cursor.close();
- return Collections.emptyMap();
- }
- Map<Long, FeedImage> result = new ArrayMap<>(imageCount);
- try {
- while (cursor.moveToNext()) {
- FeedImage image = FeedImage.fromCursor(cursor);
- result.put(image.getId(), image);
- }
- } finally {
- cursor.close();
- }
- return result;
- }
-
- /**
* Searches the DB for a FeedMedia of the given id.
*
* @param mediaId The id of the object
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 573954412..da500fd3e 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
@@ -4,6 +4,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.util.ArrayList;
@@ -38,6 +40,7 @@ import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import static android.content.Context.MODE_PRIVATE;
@@ -123,16 +126,13 @@ public final class DBTasks {
media);
}
}
- // Start playback Service
- Intent launchIntent = new Intent(context, PlaybackService.class);
- launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
- startWhenPrepared);
- launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- shouldStream);
- launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
- true);
- context.startService(launchIntent);
+
+ new PlaybackServiceStarter(context, media)
+ .callEvenIfRunning(true)
+ .startWhenPrepared(startWhenPrepared)
+ .shouldStream(shouldStream)
+ .start();
+
if (showPlayer) {
// Launch media player
context.startActivity(PlaybackService.getPlayerActivityIntent(
@@ -155,42 +155,56 @@ public final class DBTasks {
* Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
* enqueuing Feeds for download from a previous call
*
- * @param context Might be used for accessing the database
- * @param feeds List of Feeds that should be refreshed.
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
*/
- public static void refreshAllFeeds(final Context context,
- final List<Feed> feeds) {
- if (isRefreshing.compareAndSet(false, true)) {
- new Thread() {
- public void run() {
- if (feeds != null) {
- refreshFeeds(context, feeds);
- } else {
- refreshFeeds(context, DBReader.getFeedList());
- }
- isRefreshing.set(false);
+ public static void refreshAllFeeds(final Context context, final List<Feed> feeds) {
+ refreshAllFeeds(context, feeds, null);
+ }
+
+ /**
+ * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
+ * enqueuing Feeds for download from a previous call
+ *
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
+ * @param callback Called after everything was added enqueued for download. Might be null.
+ */
+ public static void refreshAllFeeds(final Context context, final List<Feed> feeds, @Nullable Runnable callback) {
+ if (!isRefreshing.compareAndSet(false, true)) {
+ Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
+ return;
+ }
- SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
- prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
+ new Thread(() -> {
+ if (feeds != null) {
+ refreshFeeds(context, feeds);
+ } else {
+ refreshFeeds(context, DBReader.getFeedList());
+ }
+ isRefreshing.set(false);
- if (FlattrUtils.hasToken()) {
- Log.d(TAG, "Flattring all pending things.");
- new FlattrClickWorker(context).executeAsync(); // flattr pending things
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
- Log.d(TAG, "Fetching flattr status.");
- new FlattrStatusFetcher(context).start();
+ if (FlattrUtils.hasToken()) {
+ Log.d(TAG, "Flattring all pending things.");
+ new FlattrClickWorker(context).executeAsync(); // flattr pending things
- }
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- GpodnetSyncService.sendSyncIntent(context);
- }
- Log.d(TAG, "refreshAllFeeds autodownload");
- autodownloadUndownloadedItems(context);
- }
- }.start();
- } else {
- Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
- }
+ Log.d(TAG, "Fetching flattr status.");
+ new FlattrStatusFetcher(context).start();
+
+ }
+ if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
+ GpodnetSyncService.sendSyncIntent(context);
+ }
+ Log.d(TAG, "refreshAllFeeds autodownload");
+ autodownloadUndownloadedItems(context);
+
+ if (callback != null) {
+ callback.run();
+ }
+ }).start();
}
/**
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
new file mode 100644
index 000000000..29ed5f7f9
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
@@ -0,0 +1,292 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.MediaMetadataRetriever;
+import android.util.Log;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+class DBUpgrader {
+ /**
+ * Upgrades the given database to a new schema version
+ */
+ static void upgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ if (oldVersion <= 1) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + PodDBAdapter.KEY_TYPE + " TEXT");
+ }
+ if (oldVersion <= 2) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + PodDBAdapter.KEY_LINK + " TEXT");
+ }
+ if (oldVersion <= 3) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_ITEM_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 4) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + PodDBAdapter.KEY_FEED_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 5) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + PodDBAdapter.KEY_REASON_DETAILED + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE + " TEXT");
+ }
+ if (oldVersion <= 6) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + PodDBAdapter.KEY_CHAPTER_TYPE + " INTEGER");
+ }
+ if (oldVersion <= 7) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE
+ + " INTEGER");
+ }
+ if (oldVersion <= 8) {
+ final int KEY_ID_POSITION = 0;
+ final int KEY_MEDIA_POSITION = 1;
+
+ // Add feeditem column to feedmedia table
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_FEEDITEM
+ + " INTEGER");
+ Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS,
+ new String[]{PodDBAdapter.KEY_ID, PodDBAdapter.KEY_MEDIA}, "? > 0",
+ new String[]{PodDBAdapter.KEY_MEDIA}, null, null, null);
+ if (feeditemCursor.moveToFirst()) {
+ db.beginTransaction();
+ ContentValues contentValues = new ContentValues();
+ do {
+ long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
+ contentValues.put(PodDBAdapter.KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
+ db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, PodDBAdapter.KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ contentValues.clear();
+ } while (feeditemCursor.moveToNext());
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+ feeditemCursor.close();
+ }
+ if (oldVersion <= 9) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ + " INTEGER DEFAULT 1");
+ }
+ if (oldVersion <= 10) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION
+ + " INTEGER");
+ }
+ if (oldVersion <= 11) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_USERNAME
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_PASSWORD
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE
+ + " INTEGER");
+ }
+ if (oldVersion <= 12) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_NEXT_PAGE_LINK + " TEXT");
+ }
+ if (oldVersion <= 13) {
+ // remove duplicate rows in "Chapters" table that were created because of a bug.
+ db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
+ "(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ PodDBAdapter.KEY_TITLE,
+ PodDBAdapter.KEY_START,
+ PodDBAdapter.KEY_FEEDITEM,
+ PodDBAdapter.KEY_LINK,
+ PodDBAdapter.KEY_CHAPTER_TYPE));
+ }
+ if (oldVersion <= 14) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = "
+ + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
+
+ // create indexes
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+ }
+ if (oldVersion <= 15) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=0");
+ Cursor c = db.rawQuery("SELECT " + PodDBAdapter.KEY_FILE_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=1 "
+ + " AND " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
+ if (c.moveToFirst()) {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ do {
+ String fileUrl = c.getString(0);
+ try {
+ mmr.setDataSource(fileUrl);
+ byte[] image = mmr.getEmbeddedPicture();
+ if (image != null) {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=1"
+ + " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
+ } else {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } while (c.moveToNext());
+ }
+ c.close();
+ }
+ if (oldVersion <= 16) {
+ String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_FEEDITEM
+ + " WHERE "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ + " = 0 AND " // unplayed
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION + " = 0 AND " // not partially played
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_ID + " IS NULL"; // not in queue
+ String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + PodDBAdapter.KEY_READ + "=" + FeedItem.NEW
+ + " WHERE " + PodDBAdapter.KEY_ID + " IN (" + selectNew + ")";
+ Log.d("Migration", "SQL: " + sql);
+ db.execSQL(sql);
+ }
+ if (oldVersion <= 17) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
+ }
+ if (oldVersion < 1030005) {
+ db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
+ "(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
+ "AND id NOT IN (SELECT feeditem FROM Queue)");
+ }
+ if (oldVersion < 1040001) {
+ db.execSQL(PodDBAdapter.CREATE_TABLE_FAVORITES);
+ }
+ if (oldVersion < 1040002) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
+ }
+ if (oldVersion < 1040013) {
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
+ }
+ if (oldVersion < 1050003) {
+ // Migrates feed list filter data
+
+ db.beginTransaction();
+
+ // Change to intermediate values to avoid overwriting in the following find/replace
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'unplayed', 'noplay')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_queued', 'noqueue')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_downloaded', 'nodl')");
+
+ // Replace played, queued, and downloaded with their opposites
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'played', 'unplayed')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'queued', 'not_queued')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'downloaded', 'not_downloaded')");
+
+ // Now replace intermediates for unplayed, not queued, etc. with their opposites
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noplay', 'played')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noqueue', 'queued')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'nodl', 'downloaded')");
+
+ // Paused doesn't have an opposite, so unplayed is the next best option
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'paused', 'unplayed')");
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ // and now get ready for autodownload filters
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ // and now auto refresh
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
+ }
+ if (oldVersion < 1050004) {
+ // prevent old timestamps to be misinterpreted as ETags
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " SET " + PodDBAdapter.KEY_LASTUPDATE + "=NULL");
+ }
+ if (oldVersion < 1060200) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_CUSTOM_TITLE + " TEXT");
+ }
+ if (oldVersion < 1060596) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
+
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ + " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_IMAGE + ")");
+
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ + " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_IMAGE + ")");
+
+ db.execSQL("DROP TABLE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES);
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index 49de7ffe7..1f4fad4ba 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -32,7 +32,6 @@ import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -166,17 +165,6 @@ public class DBWriter {
editor.commit();
}
- // delete image file
- if (feed.getImage() != null) {
- if (feed.getImage().isDownloaded()
- && feed.getImage().getFile_url() != null) {
- File imageFile = new File(feed.getImage()
- .getFile_url());
- imageFile.delete();
- } else if (requester.isDownloadingFile(feed.getImage())) {
- requester.cancelDownload(context, feed.getImage());
- }
- }
// delete stored media files and mark them as read
List<FeedItem> queue = DBReader.getQueue();
List<FeedItem> removed = new ArrayList<>();
@@ -188,6 +176,9 @@ public class DBWriter {
if(queue.remove(item)) {
removed.add(item);
}
+ if (item.getState() == FeedItem.State.PLAYING && PlaybackService.isRunning) {
+ context.stopService(new Intent(context, PlaybackService.class));
+ }
if (item.getMedia() != null
&& item.getMedia().isDownloaded()) {
File mediaFile = new File(item.getMedia()
@@ -197,16 +188,6 @@ public class DBWriter {
&& requester.isDownloadingFile(item.getMedia())) {
requester.cancelDownload(context, item.getMedia());
}
-
- if (item.hasItemImage()) {
- FeedImage image = item.getImage();
- if (image.isDownloaded() && image.getFile_url() != null) {
- File imgFile = new File(image.getFile_url());
- imgFile.delete();
- } else if (requester.isDownloadingFile(image)) {
- requester.cancelDownload(context, item.getImage());
- }
- }
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -783,21 +764,6 @@ public class DBWriter {
}
/**
- * Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The
- * contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved.
- *
- * @param image The FeedImage object.
- */
- public static Future<?> setFeedImage(final FeedImage image) {
- return dbExec.submit(() -> {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setImage(image);
- adapter.close();
- });
- }
-
- /**
* Updates download URL of a feed
*/
public static Future<?> updateFeedDownloadURL(final String original, final String updated) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
index a8fd79fda..7d4b737db 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
@@ -81,7 +82,7 @@ public class DownloadRequester {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
- context.startService(launchIntent);
+ ContextCompat.startForegroundService(context, launchIntent);
return true;
}
@@ -176,8 +177,8 @@ public class DownloadRequester {
args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
args.putBoolean(REQUEST_ARG_LOAD_ALL_PAGES, loadAllPages);
- download(context, feed, null, new File(getFeedfilePath(context),
- getFeedfileName(feed)), true, username, password, lastModified, true, args);
+ download(context, feed, null, new File(getFeedfilePath(), getFeedfileName(feed)),
+ true, username, password, lastModified, true, args);
}
}
@@ -203,8 +204,7 @@ public class DownloadRequester {
if (feedmedia.getFile_url() != null) {
dest = new File(feedmedia.getFile_url());
} else {
- dest = new File(getMediafilePath(context, feedmedia),
- getMediafilename(feedmedia));
+ dest = new File(getMediafilePath(feedmedia), getMediafilename(feedmedia));
}
download(context, feedmedia, feed,
dest, false, username, password, null, false, null);
@@ -305,10 +305,8 @@ public class DownloadRequester {
return downloads.size();
}
- private synchronized String getFeedfilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
- .toString() + "/";
+ private synchronized String getFeedfilePath() throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(FEED_DOWNLOADPATH).toString() + "/";
}
private synchronized String getFeedfileName(Feed feed) {
@@ -319,10 +317,8 @@ public class DownloadRequester {
return "feed-" + FileNameGenerator.generateFileName(filename);
}
- private synchronized String getMediafilePath(Context context, FeedMedia media)
- throws DownloadRequestException {
+ private synchronized String getMediafilePath(FeedMedia media) throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
- context,
MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(media.getItem()
.getFeed().getTitle()) + "/"
@@ -330,8 +326,7 @@ public class DownloadRequester {
return externalStorage.toString();
}
- private File getExternalFilesDirOrThrowException(Context context,
- String type) throws DownloadRequestException {
+ private File getExternalFilesDirOrThrowException(String type) throws DownloadRequestException {
File result = UserPreferences.getDataFolder(type);
if (result == null) {
throw new DownloadRequestException(
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 e82252310..591015dff 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
@@ -14,20 +14,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever;
import android.text.TextUtils;
import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.ProgressEvent;
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.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -38,6 +28,13 @@ import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.greenrobot.event.EventBus;
import org.apache.commons.io.FileUtils;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
// TODO Remove media column from feeditem table
/**
@@ -74,8 +71,9 @@ public class PodDBAdapter {
public static final String KEY_SIZE = "filesize";
public static final String KEY_MIME_TYPE = "mime_type";
public static final String KEY_IMAGE = "image";
+ public static final String KEY_IMAGE_URL = "image_url";
public static final String KEY_FEED = "feed";
- private static final String KEY_MEDIA = "media";
+ public static final String KEY_MEDIA = "media";
public static final String KEY_DOWNLOADED = "downloaded";
public static final String KEY_LASTUPDATE = "last_update";
public static final String KEY_FEEDFILE = "feedfile";
@@ -114,14 +112,14 @@ public class PodDBAdapter {
public static final String KEY_EXCLUDE_FILTER = "exclude_filter";
// Table names
- private static final String TABLE_NAME_FEEDS = "Feeds";
- private static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
- private static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
- private static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
- private static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
- private static final String TABLE_NAME_QUEUE = "Queue";
- private static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
- private static final String TABLE_NAME_FAVORITES = "Favorites";
+ static final String TABLE_NAME_FEEDS = "Feeds";
+ static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
+ static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
+ static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
+ static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
+ static final String TABLE_NAME_QUEUE = "Queue";
+ static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
+ static final String TABLE_NAME_FAVORITES = "Favorites";
// SQL Statements for creating new tables
private static final String TABLE_PRIMARY_KEY = KEY_ID
@@ -133,7 +131,7 @@ public class PodDBAdapter {
+ KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
- + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
+ + " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT,"
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1,"
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_USERNAME + " TEXT,"
@@ -155,14 +153,9 @@ public class PodDBAdapter {
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER,"
- + KEY_IMAGE + " INTEGER,"
+ + KEY_IMAGE_URL + " TEXT,"
+ KEY_AUTO_DOWNLOAD + " INTEGER)";
- private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
- + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
- + KEY_DOWNLOADED + " INTEGER)";
-
private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
+ TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
+ " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
@@ -191,36 +184,31 @@ public class PodDBAdapter {
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
// SQL Statements for creating indexes
- private static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")";
- private static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
- + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
- + KEY_IMAGE + ")";
-
- private static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_PUBDATE + ")";
- private static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_READ + ")";
-
- private static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")";
- private static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ KEY_FEEDITEM + ")";
- private static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ KEY_FEEDITEM + ")";
- private static final String CREATE_TABLE_FAVORITES = "CREATE TABLE "
+ static final String CREATE_TABLE_FAVORITES = "CREATE TABLE "
+ TABLE_NAME_FAVORITES + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
@@ -240,7 +228,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE,
TABLE_NAME_FEEDS + "." + KEY_LANGUAGE,
TABLE_NAME_FEEDS + "." + KEY_AUTHOR,
- TABLE_NAME_FEEDS + "." + KEY_IMAGE,
+ TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEEDS + "." + KEY_TYPE,
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
@@ -273,7 +261,7 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
- TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
};
@@ -283,7 +271,6 @@ public class PodDBAdapter {
private static final String[] ALL_TABLES = {
TABLE_NAME_FEEDS,
TABLE_NAME_FEED_ITEMS,
- TABLE_NAME_FEED_IMAGES,
TABLE_NAME_FEED_MEDIA,
TABLE_NAME_DOWNLOAD_LOG,
TABLE_NAME_QUEUE,
@@ -388,12 +375,7 @@ public class PodDBAdapter {
values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
values.put(KEY_AUTHOR, feed.getAuthor());
values.put(KEY_LANGUAGE, feed.getLanguage());
- if (feed.getImage() != null) {
- if (feed.getImage().getId() == 0) {
- setImage(feed.getImage());
- }
- values.put(KEY_IMAGE, feed.getImage().getId());
- }
+ values.put(KEY_IMAGE_URL, feed.getImageUrl());
values.put(KEY_FILE_URL, feed.getFile_url());
values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
@@ -450,54 +432,7 @@ public class PodDBAdapter {
}
/**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- */
- public long setImage(FeedImage image) {
- boolean startedTransaction = false;
-
- try {
- if (!db.inTransaction()) {
- db.beginTransactionNonExclusive();
- startedTransaction = true;
- }
-
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, image.getTitle());
- values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
- values.put(KEY_DOWNLOADED, image.isDownloaded());
- values.put(KEY_FILE_URL, image.getFile_url());
- if (image.getId() == 0) {
- image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
- } else {
- db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
- new String[]{String.valueOf(image.getId())});
- }
-
- final FeedComponent owner = image.getOwner();
- if (owner != null && owner.getId() != 0) {
- values.clear();
- values.put(KEY_IMAGE, image.getId());
- if (owner instanceof Feed) {
- db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
- }
- }
- if (startedTransaction) {
- db.setTransactionSuccessful();
- }
- } catch (SQLException e) {
- Log.e(TAG, Log.getStackTraceString(e));
- } finally {
- if (startedTransaction) {
- db.endTransaction();
- }
- }
- return image.getId();
- }
-
- /**
- * Inserts or updates an image entry
+ * Inserts or updates a media entry
*
* @return the id of the entry
*/
@@ -759,12 +694,7 @@ public class PodDBAdapter {
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload());
- if (item.hasItemImage()) {
- if (item.getImage().getId() == 0) {
- setImage(item.getImage());
- }
- values.put(KEY_IMAGE, item.getImage().getId());
- }
+ values.put(KEY_IMAGE_URL, item.getImageUrl());
if (item.getId() == 0) {
item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
@@ -993,11 +923,6 @@ public class PodDBAdapter {
new String[]{String.valueOf(item.getId())});
}
- private void removeFeedImage(FeedImage image) {
- db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
- new String[]{String.valueOf(image.getId())});
- }
-
/**
* Remove a FeedItem and its FeedMedia entry.
*/
@@ -1008,9 +933,6 @@ public class PodDBAdapter {
if (item.hasChapters() || item.getChapters() != null) {
removeChaptersOfItem(item);
}
- if (item.hasItemImage()) {
- removeFeedImage(item.getImage());
- }
db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
new String[]{String.valueOf(item.getId())});
}
@@ -1021,9 +943,6 @@ public class PodDBAdapter {
public void removeFeed(Feed feed) {
try {
db.beginTransactionNonExclusive();
- if (feed.getImage() != null) {
- removeFeedImage(feed.getImage());
- }
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
@@ -1363,17 +1282,12 @@ public class PodDBAdapter {
public Cursor getImageAuthenticationCursor(final String imageUrl) {
String downloadUrl = DatabaseUtils.sqlEscapeString(imageUrl);
final String query = ""
- + "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES
+ + "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEEDS
- + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE
- + " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl
- + " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD
- + " FROM " + TABLE_NAME_FEED_IMAGES
- + " INNER JOIN " + TABLE_NAME_FEED_ITEMS
- + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE
- + " INNER JOIN " + TABLE_NAME_FEEDS
- + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl;
+ + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + " = " + TABLE_NAME_FEEDS + "." + KEY_ID
+ + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + "=" + downloadUrl
+ + " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEEDS
+ + " WHERE " + TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL + "=" + downloadUrl;
return db.rawQuery(query, null);
}
@@ -1672,7 +1586,7 @@ public class PodDBAdapter {
*/
private static class PodDBHelper extends SQLiteOpenHelper {
- private static final int VERSION = 1060200;
+ private static final int VERSION = 1060596;
private final Context context;
@@ -1693,7 +1607,6 @@ public class PodDBAdapter {
public void onCreate(final SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_FEEDS);
db.execSQL(CREATE_TABLE_FEED_ITEMS);
- db.execSQL(CREATE_TABLE_FEED_IMAGES);
db.execSQL(CREATE_TABLE_FEED_MEDIA);
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
@@ -1701,7 +1614,6 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_FAVORITES);
db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
- db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
db.execSQL(CREATE_INDEX_FEEDITEMS_PUBDATE);
db.execSQL(CREATE_INDEX_FEEDITEMS_READ);
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
@@ -1716,263 +1628,7 @@ public class PodDBAdapter {
EventBus.getDefault().post(ProgressEvent.start(context.getString(R.string.progress_upgrading_database)));
Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
+ newVersion + ".");
- if (oldVersion <= 1) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_TYPE + " TEXT");
- }
- if (oldVersion <= 2) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_LINK + " TEXT");
- }
- if (oldVersion <= 3) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 4) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_FEED_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 5) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
- }
- if (oldVersion <= 6) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
- }
- if (oldVersion <= 7) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
- + " INTEGER");
- }
- if (oldVersion <= 8) {
- final int KEY_ID_POSITION = 0;
- final int KEY_MEDIA_POSITION = 1;
-
- // Add feeditem column to feedmedia table
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_FEEDITEM
- + " INTEGER");
- Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS,
- new String[]{KEY_ID, KEY_MEDIA}, "? > 0",
- new String[]{KEY_MEDIA}, null, null, null);
- if (feeditemCursor.moveToFirst()) {
- db.beginTransaction();
- ContentValues contentValues = new ContentValues();
- do {
- long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
- contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
- db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
- contentValues.clear();
- } while (feeditemCursor.moveToNext());
- db.setTransactionSuccessful();
- db.endTransaction();
- }
- feeditemCursor.close();
- }
- if (oldVersion <= 9) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_AUTO_DOWNLOAD
- + " INTEGER DEFAULT 1");
- }
- if (oldVersion <= 10) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_FLATTR_STATUS
- + " INTEGER");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_FLATTR_STATUS
- + " INTEGER");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_PLAYED_DURATION
- + " INTEGER");
- }
- if (oldVersion <= 11) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_USERNAME
- + " TEXT");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_PASSWORD
- + " TEXT");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_IMAGE
- + " INTEGER");
- }
- if (oldVersion <= 12) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_IS_PAGED + " INTEGER DEFAULT 0");
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_NEXT_PAGE_LINK + " TEXT");
- }
- if (oldVersion <= 13) {
- // remove duplicate rows in "Chapters" table that were created because of a bug.
- db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
- "(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
- PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
- KEY_ID,
- KEY_ID,
- KEY_ID,
- PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
- KEY_TITLE,
- KEY_START,
- KEY_FEEDITEM,
- KEY_LINK,
- KEY_CHAPTER_TYPE));
- }
- if (oldVersion <= 14) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_AUTO_DOWNLOAD + " INTEGER");
- db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " SET " + KEY_AUTO_DOWNLOAD + " = "
- + "(SELECT " + KEY_AUTO_DOWNLOAD
- + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
- + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + KEY_ID
- + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + ")");
-
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_HIDE + " TEXT");
-
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
-
- // create indexes
- db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
- db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE);
- db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
- db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
- db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
- }
- if (oldVersion <= 15) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
- db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
- + " WHERE " + KEY_DOWNLOADED + "=0");
- Cursor c = db.rawQuery("SELECT " + KEY_FILE_URL
- + " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " WHERE " + KEY_DOWNLOADED + "=1 "
- + " AND " + KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
- if (c.moveToFirst()) {
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- do {
- String fileUrl = c.getString(0);
- try {
- mmr.setDataSource(fileUrl);
- byte[] image = mmr.getEmbeddedPicture();
- if (image != null) {
- db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=1"
- + " WHERE " + KEY_FILE_URL + "='" + fileUrl + "'");
- } else {
- db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
- + " WHERE " + KEY_FILE_URL + "='" + fileUrl + "'");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- } while (c.moveToNext());
- }
- c.close();
- }
- if (oldVersion <= 16) {
- String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID
- + " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
- + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
- + " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
- + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
- + PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
- + " WHERE "
- + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
- + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
- + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
- + PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
- String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
- + " SET " + KEY_READ + "=" + FeedItem.NEW
- + " WHERE " + KEY_ID + " IN (" + selectNew + ")";
- Log.d("Migration", "SQL: " + sql);
- db.execSQL(sql);
- }
- if (oldVersion <= 17) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
- }
- if (oldVersion < 1030005) {
- db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
- "(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
- "AND id NOT IN (SELECT feeditem FROM Queue)");
- }
- if (oldVersion < 1040001) {
- db.execSQL(CREATE_TABLE_FAVORITES);
- }
- if (oldVersion < 1040002) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
- }
- if (oldVersion < 1040013) {
- db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
- db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
- }
- if (oldVersion < 1050003) {
- // Migrates feed list filter data
-
- db.beginTransaction();
-
- // Change to intermediate values to avoid overwriting in the following find/replace
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'unplayed', 'noplay')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_queued', 'noqueue')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_downloaded', 'nodl')");
-
- // Replace played, queued, and downloaded with their opposites
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'played', 'unplayed')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'queued', 'not_queued')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'downloaded', 'not_downloaded')");
-
- // Now replace intermediates for unplayed, not queued, etc. with their opposites
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noplay', 'played')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noqueue', 'queued')");
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'nodl', 'downloaded')");
-
- // Paused doesn't have an opposite, so unplayed is the next best option
- db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
- "SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'paused', 'unplayed')");
-
- db.setTransactionSuccessful();
- db.endTransaction();
-
- // and now get ready for autodownload filters
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
-
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
-
- // and now auto refresh
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
- }
- if (oldVersion < 1050004) {
- // prevent old timestamps to be misinterpreted as ETags
- db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " SET " + PodDBAdapter.KEY_LASTUPDATE + "=NULL");
- }
- if (oldVersion < 1060200) {
- db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
- + " ADD COLUMN " + PodDBAdapter.KEY_CUSTOM_TITLE + " TEXT");
- }
-
+ DBUpgrader.upgrade(db, oldVersion, newVersion);
EventBus.getDefault().post(ProgressEvent.end());
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
index 670e99fce..18af0800f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java
@@ -7,7 +7,6 @@ import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
public class NSITunes extends Namespace {
@@ -16,7 +15,6 @@ public class NSITunes extends Namespace {
public static final String NSURI = "http://www.itunes.com/dtds/podcast-1.0.dtd";
private static final String IMAGE = "image";
- private static final String IMAGE_TITLE = "image";
private static final String IMAGE_HREF = "href";
private static final String AUTHOR = "author";
@@ -29,21 +27,15 @@ public class NSITunes extends Namespace {
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
if (IMAGE.equals(localName)) {
- FeedImage image = new FeedImage();
- image.setTitle(IMAGE_TITLE);
- image.setDownload_url(attributes.getValue(IMAGE_HREF));
+ String url = attributes.getValue(IMAGE_HREF);
if (state.getCurrentItem() != null) {
- // this is an items image
- image.setTitle(state.getCurrentItem().getTitle() + IMAGE_TITLE);
- image.setOwner(state.getCurrentItem());
- state.getCurrentItem().setImage(image);
+ state.getCurrentItem().setImageUrl(url);
} else {
// this is the feed image
// prefer to all other images
- if (!TextUtils.isEmpty(image.getDownload_url())) {
- image.setOwner(state.getFeed());
- state.getFeed().setImage(image);
+ if (!TextUtils.isEmpty(url)) {
+ state.getFeed().setImageUrl(url);
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
index f2cfc2e57..638383223 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
@@ -7,7 +7,6 @@ import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.namespace.atom.AtomText;
@@ -94,25 +93,16 @@ public class NSMedia extends Namespace {
}
state.getCurrentItem().setMedia(media);
} else if (state.getCurrentItem() != null && url != null && validTypeImage) {
- FeedImage image = new FeedImage();
- image.setDownload_url(url);
- image.setOwner(state.getCurrentItem());
-
- state.getCurrentItem().setImage(image);
+ state.getCurrentItem().setImageUrl(url);
}
} else if (IMAGE.equals(localName)) {
String url = attributes.getValue(IMAGE_URL);
if (url != null) {
- FeedImage image = new FeedImage();
- image.setDownload_url(url);
-
if (state.getCurrentItem() != null) {
- image.setOwner(state.getCurrentItem());
- state.getCurrentItem().setImage(image);
+ state.getCurrentItem().setImageUrl(url);
} else {
- if (state.getFeed().getImage() == null) {
- image.setOwner(state.getFeed());
- state.getFeed().setImage(image);
+ if (state.getFeed().getImageUrl() == null) {
+ state.getFeed().setImageUrl(url);
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
index 3d752df76..a1100a976 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
@@ -6,7 +6,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@@ -77,17 +76,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setMedia(media);
}
- } else if (IMAGE.equals(localName)) {
- if (state.getTagstack().size() >= 1) {
- String parent = state.getTagstack().peek().getName();
- if (CHANNEL.equals(parent)) {
- Feed feed = state.getFeed();
- if(feed != null && feed.getImage() == null) {
- feed.setImage(new FeedImage());
- feed.getImage().setOwner(state.getFeed());
- }
- }
- }
}
return new SyndElement(localName, this);
}
@@ -134,11 +122,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setTitle(title);
} else if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setTitle(title);
- } else if (IMAGE.equals(second) && CHANNEL.equals(third)) {
- if(state.getFeed() != null && state.getFeed().getImage() != null &&
- state.getFeed().getImage().getTitle() == null) {
- state.getFeed().getImage().setTitle(title);
- }
}
} else if (LINK.equals(top)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {
@@ -150,9 +133,8 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (URL.equals(top) && IMAGE.equals(second) && CHANNEL.equals(third)) {
// prefer itunes:image
- if(state.getFeed() != null && state.getFeed().getImage() != null &&
- state.getFeed().getImage().getDownload_url() == null) {
- state.getFeed().getImage().setDownload_url(content);
+ if (state.getFeed() != null) {
+ state.getFeed().setImageUrl(content);
}
} else if (DESCR.equals(localName)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
index 1fe388d9d..aab1b1a5b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
@@ -5,7 +5,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@@ -210,10 +209,10 @@ public class NSAtom extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (PUBLISHED.equals(top) && ENTRY.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
- } else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImage() == null) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ } else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImageUrl() == null) {
+ state.getFeed().setImageUrl(content);
} else if (IMAGE_ICON.equals(top) && state.getFeed() != null) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ state.getFeed().setImageUrl(content);
} else if (AUTHOR_NAME.equals(top) && AUTHOR.equals(second) &&
state.getFeed() != null && state.getCurrentItem() == null) {
String currentName = state.getFeed().getAuthor();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
index 892e5ff38..76a6549ae 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java
@@ -75,4 +75,18 @@ public class FeedItemUtil {
return false;
}
+ /**
+ * Get the link for the feed item for the purpose of Share. It fallbacks to
+ * use the feed's link if the named feed item has no link.
+ */
+ public static String getLinkWithFallback(FeedItem item) {
+ if (item == null) {
+ return null;
+ } else if (item.getLink() != null) {
+ return item.getLink();
+ } else if (item.getFeed() != null) {
+ return item.getFeed().getLink();
+ }
+ return null;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
new file mode 100644
index 000000000..24e0da9ed
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
@@ -0,0 +1,21 @@
+package de.danoeh.antennapod.core.util;
+
+import android.content.Context;
+import android.util.Log;
+import de.danoeh.antennapod.core.storage.DBTasks;
+
+public class FeedUpdateUtils {
+ private static final String TAG = "FeedUpdateUtils";
+
+ private FeedUpdateUtils() {
+
+ }
+
+ public static void startAutoUpdate(Context context, Runnable callback) {
+ if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
+ DBTasks.refreshAllFeeds(context, null, callback);
+ } else {
+ Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
index 898f7bedb..5ae00460e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
@@ -50,11 +50,15 @@ public class ShareUtils {
return item.getFeed().getTitle() + ": " + item.getTitle();
}
+ public static boolean hasLinkToShare(FeedItem item) {
+ return FeedItemUtil.getLinkWithFallback(item) != null;
+ }
+
public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
- String text = getItemShareText(item) + " " + item.getLink();
+ String text = getItemShareText(item) + " " + FeedItemUtil.getLinkWithFallback(item);
if(withPosition) {
int pos = item.getMedia().getPosition();
- text = item.getLink() + " [" + Converter.getDurationStringLong(pos) + "]";
+ text += " [" + Converter.getDurationStringLong(pos) + "]";
}
shareLink(context, text);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
index f67367643..03d0f10ff 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
@@ -1,7 +1,12 @@
package de.danoeh.antennapod.core.util;
+import android.content.Context;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
import android.util.Log;
+import android.util.TypedValue;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -12,6 +17,8 @@ public class ThemeUtils {
int theme = UserPreferences.getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.color.selection_background_color_dark;
+ } else if (theme == R.style.Theme_AntennaPod_TrueBlack){
+ return R.color.selection_background_color_trueblack;
} else if (theme == R.style.Theme_AntennaPod_Light) {
return R.color.selection_background_color_light;
} else {
@@ -20,4 +27,10 @@ public class ThemeUtils {
return R.color.selection_background_color_light;
}
}
+
+ public static @ColorInt int getColorFromAttr(Context context, @AttrRes int attr) {
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(attr, typedValue, true);
+ return typedValue.data;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
new file mode 100644
index 000000000..ad723c685
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
@@ -0,0 +1,155 @@
+package de.danoeh.antennapod.core.util.download;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.service.FeedUpdateJobService;
+
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+public class AutoUpdateManager {
+ private static final int JOB_ID_FEED_UPDATE = 42;
+ private static final String TAG = "AutoUpdateManager";
+
+ private AutoUpdateManager() {
+
+ }
+
+ /**
+ * Sets the interval in which the feeds are refreshed automatically
+ */
+ public static void restartUpdateIntervalAlarm(Context context, long triggerAtMillis, long intervalMillis) {
+ Log.d(TAG, "Restarting update alarm.");
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ restartJobServiceInterval(context, intervalMillis);
+ } else {
+ restartAlarmManagerInterval(context, triggerAtMillis, intervalMillis);
+ }
+ }
+
+ /**
+ * Sets time of day the feeds are refreshed automatically
+ */
+ public static void restartUpdateTimeOfDayAlarm(Context context, int hoursOfDay, int minute) {
+ Log.d(TAG, "Restarting update alarm.");
+
+ Calendar now = Calendar.getInstance();
+ Calendar alarm = (Calendar)now.clone();
+ alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
+ alarm.set(Calendar.MINUTE, minute);
+ if (alarm.before(now) || alarm.equals(now)) {
+ alarm.add(Calendar.DATE, 1);
+ }
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ long triggerAtMillis = alarm.getTimeInMillis() - now.getTimeInMillis();
+ restartJobServiceTriggerAt(context, triggerAtMillis);
+ } else {
+ restartAlarmManagerTimeOfDay(context, alarm);
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static JobInfo.Builder getFeedUpdateJobBuilder(Context context) {
+ ComponentName serviceComponent = new ComponentName(context, FeedUpdateJobService.class);
+ JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_FEED_UPDATE, serviceComponent);
+ builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ builder.setPersisted(true);
+ return builder;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ private static void restartJobServiceInterval(Context context, long intervalMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Log.d(TAG, "JobScheduler was null.");
+ return;
+ }
+
+ JobInfo oldJob = jobScheduler.getPendingJob(JOB_ID_FEED_UPDATE);
+ if (oldJob != null && oldJob.getIntervalMillis() == intervalMillis) {
+ Log.d(TAG, "JobScheduler was already set at interval " + intervalMillis + ", ignoring.");
+ return;
+ }
+
+ JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
+ builder.setPeriodic(intervalMillis);
+ jobScheduler.cancel(JOB_ID_FEED_UPDATE);
+
+ if (intervalMillis <= 0) {
+ Log.d(TAG, "Automatic update was deactivated");
+ return;
+ }
+
+ jobScheduler.schedule(builder.build());
+ Log.d(TAG, "JobScheduler was set at interval " + intervalMillis);
+ }
+
+ private static void restartAlarmManagerInterval(Context context, long triggerAtMillis, long intervalMillis) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ if (alarmManager == null) {
+ Log.d(TAG, "AlarmManager was null");
+ return;
+ }
+
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, FeedUpdateReceiver.class), 0);
+ alarmManager.cancel(updateIntent);
+
+ if (intervalMillis <= 0) {
+ Log.d(TAG, "Automatic update was deactivated");
+ return;
+ }
+
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + triggerAtMillis,
+ updateIntent);
+ Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ private static void restartJobServiceTriggerAt(Context context, long triggerAtMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Log.d(TAG, "JobScheduler was null.");
+ return;
+ }
+
+ JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
+ builder.setMinimumLatency(triggerAtMillis);
+ jobScheduler.cancel(JOB_ID_FEED_UPDATE);
+ jobScheduler.schedule(builder.build());
+ Log.d(TAG, "JobScheduler was set for " + triggerAtMillis);
+ }
+
+ private static void restartAlarmManagerTimeOfDay(Context context, Calendar alarm) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ if (alarmManager == null) {
+ Log.d(TAG, "AlarmManager was null");
+ return;
+ }
+
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, FeedUpdateReceiver.class), 0);
+ alarmManager.cancel(updateIntent);
+
+ Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
+ alarmManager.set(AlarmManager.RTC_WAKEUP,
+ alarm.getTimeInMillis(),
+ updateIntent);
+ Log.d(TAG, "Changed alarm to new time of day " + alarm.get(Calendar.HOUR_OF_DAY) + ":" + alarm.get(Calendar.MINUTE));
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
new file mode 100644
index 000000000..1c42364ea
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
@@ -0,0 +1,62 @@
+package de.danoeh.antennapod.core.util.gui;
+
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import de.danoeh.antennapod.core.R;
+
+public class NotificationUtils {
+ public static final String CHANNEL_ID_USER_ACTION = "user_action";
+ public static final String CHANNEL_ID_DOWNLOADING = "downloading";
+ public static final String CHANNEL_ID_PLAYING = "playing";
+ public static final String CHANNEL_ID_ERROR = "error";
+
+ public static void createChannels(Context context) {
+ if (android.os.Build.VERSION.SDK_INT < 26) {
+ return;
+ }
+ NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (mNotificationManager != null) {
+ mNotificationManager.createNotificationChannel(createChannelUserAction(context));
+ mNotificationManager.createNotificationChannel(createChannelDownloading(context));
+ mNotificationManager.createNotificationChannel(createChannelPlaying(context));
+ mNotificationManager.createNotificationChannel(createChannelError(context));
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelUserAction(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_USER_ACTION,
+ c.getString(R.string.notification_channel_user_action), NotificationManager.IMPORTANCE_HIGH);
+ mChannel.setDescription(c.getString(R.string.notification_channel_user_action_description));
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelDownloading(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_DOWNLOADING,
+ c.getString(R.string.notification_channel_downloading), NotificationManager.IMPORTANCE_LOW);
+ mChannel.setDescription(c.getString(R.string.notification_channel_downloading_description));
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelPlaying(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_PLAYING,
+ c.getString(R.string.notification_channel_playing), NotificationManager.IMPORTANCE_LOW);
+ mChannel.setDescription(c.getString(R.string.notification_channel_playing_description));
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelError(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_ERROR,
+ c.getString(R.string.notification_channel_error), NotificationManager.IMPORTANCE_HIGH);
+ mChannel.setDescription(c.getString(R.string.notification_channel_error_description));
+ return mChannel;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
index 279c56338..ff7f5b79d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
@@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcelable;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
@@ -11,6 +13,7 @@ import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -179,6 +182,23 @@ public interface Playable extends Parcelable,
* Restores a playable object from a sharedPreferences file. This method might load data from the database,
* depending on the type of playable that was restored.
*
+ * @return The restored Playable object
+ */
+ @Nullable
+ public static Playable createInstanceFromPreferences(Context context) {
+ long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
+ if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ return PlayableUtils.createInstanceFromPreferences(context,
+ (int) currentlyPlayingMedia, prefs);
+ }
+ return null;
+ }
+
+ /**
+ * Restores a playable object from a sharedPreferences file. This method might load data from the database,
+ * depending on the type of playable that was restored.
+ *
* @param type An integer that represents the type of the Playable object
* that is restored.
* @param pref The SharedPreferences file from which the Playable object
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 a160b4f0a..a3f02d5cc 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
@@ -14,6 +14,8 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -59,7 +61,7 @@ public abstract class PlaybackController {
private PlaybackService playbackService;
private Playable media;
- private PlayerStatus status;
+ private PlayerStatus status = PlayerStatus.STOPPED;
private final ScheduledThreadPoolExecutor schedExecutor;
private static final int SCHED_EX_POOLSIZE = 1;
@@ -69,6 +71,7 @@ public abstract class PlaybackController {
private boolean mediaInfoLoaded = false;
private boolean released = false;
+ private boolean initialized = false;
private Subscription serviceBinder;
@@ -92,10 +95,22 @@ public abstract class PlaybackController {
}
/**
- * Creates a new connection to the playbackService. Should be called in the
- * activity's onResume() method.
+ * Creates a new connection to the playbackService.
*/
public void init() {
+ if (PlaybackService.isRunning) {
+ initServiceRunning();
+ } else {
+ initServiceNotRunning();
+ }
+ }
+
+ private synchronized void initServiceRunning() {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+
activity.registerReceiver(statusUpdate, new IntentFilter(
PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
@@ -167,7 +182,7 @@ public abstract class PlaybackController {
*/
private void bindToService() {
Log.d(TAG, "Trying to connect to service");
- if(serviceBinder != null) {
+ if (serviceBinder != null) {
serviceBinder.unsubscribe();
}
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
@@ -178,7 +193,7 @@ public abstract class PlaybackController {
if (!PlaybackService.started) {
if (intent != null) {
Log.d(TAG, "Calling start service");
- activity.startService(intent);
+ ContextCompat.startForegroundService(activity, intent);
bound = activity.bindService(intent, mConnection, 0);
} else {
status = PlayerStatus.STOPPED;
@@ -198,31 +213,24 @@ public abstract class PlaybackController {
* Returns an intent that starts the PlaybackService and plays the last
* played media or null if no last played media could be found.
*/
- private Intent getPlayLastPlayedMediaIntent() {
+ @Nullable private Intent getPlayLastPlayedMediaIntent() {
Log.d(TAG, "Trying to restore last played media");
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
- activity.getApplicationContext());
- long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
- if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
- Playable media = PlayableUtils.createInstanceFromPreferences(activity,
- (int) currentlyPlayingMedia, prefs);
- if (media != null) {
- Intent serviceIntent = new Intent(activity, PlaybackService.class);
- serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- serviceIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, false);
- serviceIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true);
- boolean fileExists = media.localFileAvailable();
- boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
- if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
- DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
- }
- serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- lastIsStream || !fileExists);
- return serviceIntent;
- }
+ Playable media = PlayableUtils.createInstanceFromPreferences(activity);
+ if (media == null) {
+ Log.d(TAG, "No last played media found");
+ return null;
}
- Log.d(TAG, "No last played media found");
- return null;
+
+ boolean fileExists = media.localFileAvailable();
+ boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
+ if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
+ DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
+ }
+
+ return new PlaybackServiceStarter(activity, media)
+ .startWhenPrepared(false)
+ .shouldStream(lastIsStream || !fileExists)
+ .getIntent();
}
@@ -511,7 +519,7 @@ public abstract class PlaybackController {
"PlaybackService has no media object. Trying to restore last played media.");
Intent serviceIntent = getPlayLastPlayedMediaIntent();
if (serviceIntent != null) {
- activity.startService(serviceIntent);
+ ContextCompat.startForegroundService(activity, serviceIntent);
}
}
*/
@@ -576,6 +584,10 @@ public abstract class PlaybackController {
public void playPause() {
if (playbackService == null) {
+ new PlaybackServiceStarter(activity, media)
+ .startWhenPrepared(true)
+ .streamIfLastWasStream()
+ .start();
Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
return;
}
@@ -609,6 +621,8 @@ public abstract class PlaybackController {
public int getPosition() {
if (playbackService != null) {
return playbackService.getCurrentPosition();
+ } else if (media != null) {
+ return media.getPosition();
} else {
return PlaybackService.INVALID_TIME;
}
@@ -617,12 +631,17 @@ public abstract class PlaybackController {
public int getDuration() {
if (playbackService != null) {
return playbackService.getDuration();
+ } else if (media != null) {
+ return media.getDuration();
} else {
return PlaybackService.INVALID_TIME;
}
}
public Playable getMedia() {
+ if (media == null) {
+ media = PlayableUtils.createInstanceFromPreferences(activity);
+ }
return media;
}
@@ -714,8 +733,13 @@ public abstract class PlaybackController {
}
public boolean isPlayingVideoLocally() {
- return playbackService != null && PlaybackService.getCurrentMediaType() == MediaType.VIDEO
- && !PlaybackService.isCasting();
+ if (PlaybackService.isCasting()) {
+ return false;
+ } else if (playbackService != null) {
+ return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
+ } else {
+ return getMedia() != null && getMedia().getMediaType() == MediaType.VIDEO;
+ }
}
public Pair<Integer, Integer> getVideoSize() {
@@ -755,6 +779,21 @@ public abstract class PlaybackController {
}
}
+ private void initServiceNotRunning() {
+ if (getMedia() == null) {
+ return;
+ }
+ if (getMedia().getMediaType() == MediaType.AUDIO) {
+ TypedArray res = activity.obtainStyledAttributes(new int[]{
+ de.danoeh.antennapod.core.R.attr.av_play_big});
+ getPlayButton().setImageResource(
+ res.getResourceId(0, de.danoeh.antennapod.core.R.drawable.ic_play_arrow_grey600_36dp));
+ res.recycle();
+ } else {
+ getPlayButton().setImageResource(R.drawable.ic_av_play_circle_outline_80dp);
+ }
+ }
+
/**
* Refreshes the current position of the media file that is playing.
*/
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
new file mode 100644
index 000000000..3ba553d12
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
@@ -0,0 +1,76 @@
+package de.danoeh.antennapod.core.util.playback;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.support.v4.content.ContextCompat;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+
+public class PlaybackServiceStarter {
+ private final Context context;
+ private final Playable media;
+ private boolean startWhenPrepared = false;
+ private boolean shouldStream = false;
+ private boolean callEvenIfRunning = false;
+ private boolean prepareImmediately = true;
+
+ public PlaybackServiceStarter(Context context, Playable media) {
+ this.context = context;
+ this.media = media;
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter shouldStream(boolean shouldStream) {
+ this.shouldStream = shouldStream;
+ return this;
+ }
+
+ public PlaybackServiceStarter streamIfLastWasStream() {
+ boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
+ return shouldStream(lastIsStream);
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter startWhenPrepared(boolean startWhenPrepared) {
+ this.startWhenPrepared = startWhenPrepared;
+ return this;
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter callEvenIfRunning(boolean callEvenIfRunning) {
+ this.callEvenIfRunning = callEvenIfRunning;
+ return this;
+ }
+
+ /**
+ * Default value: true
+ */
+ public PlaybackServiceStarter prepareImmediately(boolean prepareImmediately) {
+ this.prepareImmediately = prepareImmediately;
+ return this;
+ }
+
+ public Intent getIntent() {
+ Intent launchIntent = new Intent(context, PlaybackService.class);
+ launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared);
+ launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
+ launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, prepareImmediately);
+
+ return launchIntent;
+ }
+
+ public void start() {
+ if (PlaybackService.isRunning && !callEvenIfRunning) {
+ return;
+ }
+ ContextCompat.startForegroundService(context, getIntent());
+ }
+}
diff --git a/core/src/main/res/drawable/progress_bar_horizontal_trueblack.xml b/core/src/main/res/drawable/progress_bar_horizontal_trueblack.xml
new file mode 100644
index 000000000..604bb2655
--- /dev/null
+++ b/core/src/main/res/drawable/progress_bar_horizontal_trueblack.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape>
+ <solid android:color="#000000"/>
+ </shape>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <solid android:color="@color/holo_blue_dark"/>
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/core/src/main/res/layout/player_widget.xml b/core/src/main/res/layout/player_widget.xml
new file mode 100644
index 000000000..4c98895a0
--- /dev/null
+++ b/core/src/main/res/layout/player_widget.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/widget_margin" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#262C31" >
+
+ <ImageButton
+ android:id="@+id/butPlay"
+ android:contentDescription="@string/play_label"
+ android:layout_width="56dp"
+ android:layout_height="match_parent"
+ android:layout_alignParentRight="true"
+ android:layout_margin="12dp"
+ android:background="@drawable/borderless_button_dark"
+ android:src="@drawable/ic_play_arrow_white_24dp" />
+
+ <LinearLayout
+ android:id="@+id/layout_left"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@id/butPlay"
+ android:background="@drawable/borderless_button_dark"
+ android:gravity="center_vertical"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/txtvTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:maxLines="1"
+ android:text="@string/no_media_playing_label"
+ android:textColor="@color/white"
+ android:textSize="@dimen/text_size_medium"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/txtvProgress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:textColor="@color/white" />
+ </LinearLayout>
+ </RelativeLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/core/src/main/res/values-v21/styles.xml b/core/src/main/res/values-v21/styles.xml
index 503337c95..c53000c4f 100644
--- a/core/src/main/res/values-v21/styles.xml
+++ b/core/src/main/res/values-v21/styles.xml
@@ -6,6 +6,19 @@
<style name="Theme.AntennaPod.Dark" parent="Theme.Base.AntennaPod.Dark">
<item name="android:windowContentTransitions">true</item>
</style>
+ <style name="Theme.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.TrueBlack">
+ <item name="android:windowContentTransitions">true</item>
+ <item name="android:navigationBarColor">@color/black</item>
+ <item name="android:colorAccent">@color/white</item>
+ <item name="android:colorPrimary">@color/black</item>
+ <item name="android:colorPrimaryDark">@color/black</item>
+ </style>
+ <style name="Theme.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.TrueBlack.NoTitle">
+ <item name="android:navigationBarColor">@color/black</item>
+ <item name="android:colorAccent">@color/white</item>
+ <item name="android:colorPrimary">@color/black</item>
+ <item name="android:colorPrimaryDark">@color/black</item>
+ </style>
<style name="Widget.AntennaPod.Button" parent="Widget.AppCompat.Button">
<item name="textAllCaps">true</item>
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 9d9a2453d..1d4363627 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -137,10 +137,12 @@
<string-array name="theme_options">
<item>@string/pref_theme_title_light</item>
<item>@string/pref_theme_title_dark</item>
+ <item>@string/pref_theme_title_trueblack</item>
</string-array>
<string-array name="theme_values">
<item>0</item>
<item>1</item>
+ <item>2</item>
</string-array>
<string-array name="nav_drawer_titles">
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index b005d4dc3..f3c2b9b7e 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -56,6 +56,7 @@
<attr name="ic_cast_disconnect" format="reference"/>
<attr name="ic_swap" format="reference"/>
<attr name="master_switch_background" format="color"/>
+ <attr name="currently_playing_background" format="color"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>
diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index c9a5b3d6a..37ad81639 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -22,6 +22,7 @@
<color name="image_readability_tint">#80000000</color>
<color name="feed_image_bg">#50000000</color>
+ <color name="selection_background_color_trueblack">#286E8A</color>
<color name="selection_background_color_dark">#286E8A</color>
<color name="selection_background_color_light">#81CFEA</color>
@@ -30,6 +31,7 @@
<color name="highlight_light">#DDDDDD</color>
<color name="highlight_dark">#414141</color>
+ <color name="highlight_trueblack">#000000</color>
<color name="antennapod_blue">#147BAF</color>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 46bac68c9..704cc077c 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -6,6 +6,7 @@
<!-- Activitiy and fragment titles -->
<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="statistics_label">Statistics</string>
<string name="add_feed_label">Add Podcast</string>
@@ -29,6 +30,7 @@
<string name="free_space_label">%1$s free</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="synchronizing">Synchronizing…</string>
<!-- Statistics fragment -->
<string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
@@ -393,6 +395,7 @@
<string name="pref_episode_cache_title">Episode Cache</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
+ <string name="pref_theme_title_trueblack">True Black</string>
<string name="pref_episode_cache_unlimited">Unlimited</string>
<string name="pref_update_interval_hours_plural">hours</string>
<string name="pref_update_interval_hours_singular">hour</string>
@@ -709,4 +712,14 @@
<string name="cast_failed_seek">Failed to seek to the new position on the cast device</string>
<string name="cast_failed_receiver_player_error">Receiver player has encountered a severe error</string>
<string name="cast_failed_media_error_skipping">Error playing media. Skipping&#8230;</string>
+
+ <!-- Notification channels -->
+ <string name="notification_channel_user_action">Action required</string>
+ <string name="notification_channel_user_action_description">Shown if your action is required, for example if you need to enter a password.</string>
+ <string name="notification_channel_downloading">Downloading</string>
+ <string name="notification_channel_downloading_description">Shown while currently downloading.</string>
+ <string name="notification_channel_playing">Currently playing</string>
+ <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>
</resources>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index 77758a223..0314ca431 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -67,6 +67,8 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_light</item>
+ <item type="attr" name="currently_playing_background">@color/highlight_light</item>
+ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.Dark" parent="Theme.Base.AntennaPod.Dark">
@@ -135,8 +137,31 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_dark</item>
+ <item type="attr" name="currently_playing_background">@color/highlight_dark</item>
+ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
+ <style name="Theme.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.TrueBlack">
+ <!-- Room for API dependent attributes -->
+ </style>
+
+ <style name="Theme.Base.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.Dark">
+ <item name="progressBarTheme">@style/ProgressBarTrueBlack</item>
+ <item type="attr" name="non_transparent_background">@color/black</item>
+ <item type="attr" name="overlay_background">@color/overlay_dark</item>
+ <item type="attr" name="overlay_drawable">@drawable/overlay_drawable_dark</item>
+ <item type="attr" name="dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
+ <item type="attr" name="dragview_float_background">@color/black</item>
+ <item type="attr" name="nav_drawer_background">@color/black</item>
+ <item name="android:textColorPrimary">@color/white</item>
+ <item name="android:color">@color/white</item>
+ <item name="android:windowBackground">@color/black</item>
+ <item name="android:actionBarStyle">@color/black</item>
+ <item name="colorPrimary">@color/black</item>
+ <item name="colorPrimaryDark">@color/black</item>
+ </style>
+
+
<style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.Base.AntennaPod.Light.NoTitle">
<!-- Room for API dependent attributes -->
</style>
@@ -204,6 +229,8 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_light</item>
+ <item type="attr" name="currently_playing_background">@color/highlight_light</item>
+ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.Dark.NoTitle" parent="Theme.Base.AntennaPod.Dark.NoTitle">
@@ -273,8 +300,32 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_dark</item>
+ <item type="attr" name="currently_playing_background">@color/highlight_dark</item>
+ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
+ </style>
+
+ <style name="Theme.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.TrueBlack.NoTitle">
+ <!-- Room for API dependent attributes -->
+ </style>
+
+ <style name="Theme.Base.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.Dark.NoTitle">
+ <item name="progressBarTheme">@style/ProgressBarTrueBlack</item>
+ <item type="attr" name="non_transparent_background">@color/black</item>
+ <item type="attr" name="overlay_background">@color/black</item>
+ <item type="attr" name="overlay_drawable">@drawable/overlay_drawable_dark</item>
+ <item type="attr" name="dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
+ <item type="attr" name="dragview_float_background">@color/black</item>
+ <item type="attr" name="nav_drawer_background">@color/black</item>
+ <item type="attr" name="currently_playing_background">@color/highlight_trueblack</item>
+ <item name="android:textColorPrimary">@color/white</item>
+ <item name="android:color">@color/white</item>
+ <item name="android:windowBackground">@color/black</item>
+ <item name="android:actionBarStyle">@color/black</item>
+ <item name="colorPrimary">@color/black</item>
+ <item name="colorPrimaryDark">@color/black</item>
</style>
+
<style name="Theme.AntennaPod.Dark.Splash" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/bg_splash</item>
</style>
@@ -362,4 +413,9 @@
<item name="android:progressDrawable">@drawable/progress_bar_horizontal_dark</item>
</style>
+ <style name="ProgressBarTrueBlack">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:progressDrawable">@drawable/progress_bar_horizontal_trueblack</item>
+ </style>
+
</resources>
diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java b/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java
index f0a7214c9..aec559a5f 100644
--- a/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java
+++ b/core/src/play/java/de/danoeh/antennapod/core/cast/CastUtils.java
@@ -13,7 +13,6 @@ import java.util.Calendar;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DBReader;
@@ -97,9 +96,9 @@ public class CastUtils {
if (subtitle != null) {
metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle);
}
- FeedImage image = feedItem.getImage();
- if (image != null && !TextUtils.isEmpty(image.getDownload_url())) {
- metadata.addImage(new WebImage(Uri.parse(image.getDownload_url())));
+
+ if (!TextUtils.isEmpty(feedItem.getImageUrl())) {
+ metadata.addImage(new WebImage(Uri.parse(feedItem.getImageUrl())));
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(media.getItem().getPubDate());
diff --git a/core/src/test/java/android/text/TextUtils.java b/core/src/test/java/android/text/TextUtils.java
new file mode 100644
index 000000000..c31234171
--- /dev/null
+++ b/core/src/test/java/android/text/TextUtils.java
@@ -0,0 +1,32 @@
+package android.text;
+
+/**
+ * A slim-down version of standard {@link android.text.TextUtils} to be used in unit tests.
+ */
+public class TextUtils {
+
+ /**
+ * Returns true if a and b are equal, including if they are both null.
+ * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
+ * both the arguments were instances of String.</i></p>
+ * @param a first CharSequence to check
+ * @param b second CharSequence to check
+ * @return true if a and b are equal
+ */
+ public static boolean equals(CharSequence a, CharSequence b) {
+ if (a == b) return true;
+ int length;
+ if (a != null && b != null && (length = a.length()) == b.length()) {
+ if (a instanceof String && b instanceof String) {
+ return a.equals(b);
+ } else {
+ for (int i = 0; i < length; i++) {
+ if (a.charAt(i) != b.charAt(i)) return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/core/src/test/java/android/util/Log.java b/core/src/test/java/android/util/Log.java
new file mode 100644
index 000000000..881d10209
--- /dev/null
+++ b/core/src/test/java/android/util/Log.java
@@ -0,0 +1,245 @@
+package android.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * A stub for {@link android.util.Log} to be used in unit tests.
+ *
+ * It outputs the log statements to standard error.
+ */
+public final class Log {
+
+ /**
+ * Priority constant for the println method; use Log.v.
+ */
+ public static final int VERBOSE = 2;
+
+ /**
+ * Priority constant for the println method; use Log.d.
+ */
+ public static final int DEBUG = 3;
+
+ /**
+ * Priority constant for the println method; use Log.i.
+ */
+ public static final int INFO = 4;
+
+ /**
+ * Priority constant for the println method; use Log.w.
+ */
+ public static final int WARN = 5;
+
+ /**
+ * Priority constant for the println method; use Log.e.
+ */
+ public static final int ERROR = 6;
+
+ /**
+ * Priority constant for the println method.
+ */
+ public static final int ASSERT = 7;
+
+ private Log() {
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int v(String tag, String msg) {
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int v(String tag, String msg, Throwable tr) {
+ return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int d(String tag, String msg) {
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int d(String tag, String msg, Throwable tr) {
+ return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Send an {@link #INFO} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int i(String tag, String msg) {
+ return println_native(LOG_ID_MAIN, INFO, tag, msg);
+ }
+
+ /**
+ * Send a {@link #INFO} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int i(String tag, String msg, Throwable tr) {
+ return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+ }
+
+ /**
+ * Send a {@link #WARN} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int w(String tag, String msg) {
+ return println_native(LOG_ID_MAIN, WARN, tag, msg);
+ }
+
+ /**
+ * Send a {@link #WARN} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int w(String tag, String msg, Throwable tr) {
+ return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+ }
+
+ /**
+ * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+ *
+ * @return true in all cases (for unit test environment)
+ */
+ public static boolean isLoggable(String tag, int level) {
+ return true;
+ }
+
+ /*
+ * Send a {@link #WARN} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param tr An exception to log
+ */
+ public static int w(String tag, Throwable tr) {
+ return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+ }
+
+ /**
+ * Send an {@link #ERROR} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int e(String tag, String msg) {
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+ }
+
+ /**
+ * Send a {@link #ERROR} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int e(String tag, String msg, Throwable tr) {
+ return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
+ }
+
+ /**
+ * Like {@link #wtf(String, String)}, but also writes to the log the full
+ * call stack.
+ * @hide
+ */
+ public static int wtfStack(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
+ }
+
+ /**
+ * Priority Constant for wtf.
+ * Added for this custom Log implementation, not in android sources.
+ */
+ private static final int WTF = 8;
+ static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
+ boolean system) {
+ return printlns(LOG_ID_MAIN, WTF, tag, msg, tr);
+ }
+
+ private static final int LOG_ID_MAIN = 0;
+
+ private static final String[] PRIORITY_ABBREV = { "0", "1", "V", "D", "I", "W", "E", "A", "WTF" };
+
+ private static int println_native(int bufID, int priority, String tag, String msg) {
+ String res = PRIORITY_ABBREV[priority] + "/" + tag + " " + msg + System.lineSeparator();
+ System.err.print(res);
+ return res.length();
+ }
+
+ private static int printlns(int bufID, int priority, String tag, String msg,
+ Throwable tr) {
+ StringWriter trSW = new StringWriter();
+ if (tr != null) {
+ trSW.append(" , Exception: ");
+ PrintWriter trPW = new PrintWriter(trSW);
+ tr.printStackTrace(trPW);
+ trPW.flush();
+ }
+ return println_native(bufID, priority, tag, msg + trSW.toString());
+ }
+
+}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemMother.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemMother.java
index 3d7c4fe5f..b78cecc23 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemMother.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemMother.java
@@ -2,14 +2,14 @@ package de.danoeh.antennapod.core.feed;
import java.util.Date;
-import static de.danoeh.antennapod.core.feed.FeedImageMother.anyFeedImage;
import static de.danoeh.antennapod.core.feed.FeedMother.anyFeed;
class FeedItemMother {
+ private static final String IMAGE_URL = "http://example.com/image";
static FeedItem anyFeedItemWithImage() {
FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), FeedItem.PLAYED, anyFeed());
- item.setImage(anyFeedImage());
+ item.setImageUrl(IMAGE_URL);
return item;
}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java
index 9e12e8ae0..e36a09f00 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedItemTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedItemTest.java
@@ -1,65 +1,60 @@
package de.danoeh.antennapod.core.feed;
-import android.test.AndroidTestCase;
+import org.junit.Before;
+import org.junit.Test;
import static de.danoeh.antennapod.core.feed.FeedItemMother.anyFeedItemWithImage;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
-public class FeedItemTest extends AndroidTestCase {
+public class FeedItemTest {
private FeedItem original;
- private FeedImage originalImage;
private FeedItem changedFeedItem;
- @Override
- protected void setUp() {
+ @Before
+ public void setUp() {
original = anyFeedItemWithImage();
- originalImage = original.getImage();
changedFeedItem = anyFeedItemWithImage();
}
+ @Test
public void testUpdateFromOther_feedItemImageDownloadUrlChanged() throws Exception {
setNewFeedItemImageDownloadUrl();
-
original.updateFromOther(changedFeedItem);
-
- feedItemImageWasUpdated();
+ assertFeedItemImageWasUpdated();
}
+ @Test
public void testUpdateFromOther_feedItemImageRemoved() throws Exception {
feedItemImageRemoved();
-
original.updateFromOther(changedFeedItem);
-
- feedItemImageWasNotUpdated();
+ assertFeedItemImageWasNotUpdated();
}
+ @Test
public void testUpdateFromOther_feedItemImageAdded() throws Exception {
- feedItemHadNoImage();
+ original.setImageUrl(null);
setNewFeedItemImageDownloadUrl();
-
original.updateFromOther(changedFeedItem);
-
- feedItemImageWasUpdated();
- }
-
- private void feedItemHadNoImage() {
- original.setImage(null);
+ assertFeedItemImageWasUpdated();
}
private void setNewFeedItemImageDownloadUrl() {
- changedFeedItem.getImage().setDownload_url("http://example.com/new_picture");
+ changedFeedItem.setImageUrl("http://example.com/new_picture");
}
private void feedItemImageRemoved() {
- changedFeedItem.setImage(null);
+ changedFeedItem.setImageUrl(null);
}
- private void feedItemImageWasUpdated() {
- assertEquals(original.getImage().getDownload_url(), changedFeedItem.getImage().getDownload_url());
+ private void assertFeedItemImageWasUpdated() {
+ assertEquals(original.getImageUrl(), changedFeedItem.getImageUrl());
}
- private void feedItemImageWasNotUpdated() {
- assertTrue(originalImage == original.getImage());
+ private void assertFeedItemImageWasNotUpdated() {
+ assertEquals(anyFeedItemWithImage().getImageUrl(), original.getImageUrl());
}
} \ No newline at end of file
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedMother.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedMother.java
index fecc8e377..f46797d28 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedMother.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedMother.java
@@ -1,13 +1,11 @@
package de.danoeh.antennapod.core.feed;
-import static de.danoeh.antennapod.core.feed.FeedImageMother.anyFeedImage;
-
class FeedMother {
+ public static final String IMAGE_URL = "http://example.com/image";
public static Feed anyFeed() {
- FeedImage image = anyFeedImage();
return new Feed(0, null, "title", "http://example.com", "This is the description",
- "http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", image,
+ "http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", IMAGE_URL,
null, "http://example.com/feed", true);
}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedTest.java
index 8067ec93f..4717041f4 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/feed/FeedTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedTest.java
@@ -1,63 +1,61 @@
package de.danoeh.antennapod.core.feed;
-import android.test.AndroidTestCase;
+import org.junit.Before;
+import org.junit.Test;
-import static de.danoeh.antennapod.core.feed.FeedImageMother.anyFeedImage;
import static de.danoeh.antennapod.core.feed.FeedMother.anyFeed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-public class FeedTest extends AndroidTestCase {
+public class FeedTest {
private Feed original;
- private FeedImage originalImage;
private Feed changedFeed;
- @Override
- protected void setUp() {
+ @Before
+ public void setUp() {
original = anyFeed();
- originalImage = original.getImage();
changedFeed = anyFeed();
}
+ @Test
public void testCompareWithOther_feedImageDownloadUrlChanged() throws Exception {
setNewFeedImageDownloadUrl();
-
feedHasChanged();
}
+ @Test
public void testCompareWithOther_sameFeedImage() throws Exception {
- changedFeed.setImage(anyFeedImage());
-
+ changedFeed.setImageUrl(FeedMother.IMAGE_URL);
feedHasNotChanged();
}
+ @Test
public void testCompareWithOther_feedImageRemoved() throws Exception {
feedImageRemoved();
-
feedHasNotChanged();
}
+ @Test
public void testUpdateFromOther_feedImageDownloadUrlChanged() throws Exception {
setNewFeedImageDownloadUrl();
-
original.updateFromOther(changedFeed);
-
feedImageWasUpdated();
}
+ @Test
public void testUpdateFromOther_feedImageRemoved() throws Exception {
feedImageRemoved();
-
original.updateFromOther(changedFeed);
-
feedImageWasNotUpdated();
}
+ @Test
public void testUpdateFromOther_feedImageAdded() throws Exception {
feedHadNoImage();
setNewFeedImageDownloadUrl();
-
original.updateFromOther(changedFeed);
-
feedImageWasUpdated();
}
@@ -66,11 +64,11 @@ public class FeedTest extends AndroidTestCase {
}
private void feedHadNoImage() {
- original.setImage(null);
+ original.setImageUrl(null);
}
private void setNewFeedImageDownloadUrl() {
- changedFeed.getImage().setDownload_url("http://example.com/new_picture");
+ changedFeed.setImageUrl("http://example.com/new_picture");
}
private void feedHasChanged() {
@@ -78,15 +76,15 @@ public class FeedTest extends AndroidTestCase {
}
private void feedImageRemoved() {
- changedFeed.setImage(null);
+ changedFeed.setImageUrl(null);
}
private void feedImageWasUpdated() {
- assertEquals(original.getImage().getDownload_url(), changedFeed.getImage().getDownload_url());
+ assertEquals(original.getImageUrl(), changedFeed.getImageUrl());
}
private void feedImageWasNotUpdated() {
- assertTrue(originalImage == original.getImage());
+ assertEquals(anyFeed().getImageUrl(), original.getImageUrl());
}
} \ No newline at end of file
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/LongLongMapTest.java
index 50c2a9c3c..0ed77eb9f 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/util/LongLongMapTest.java
@@ -1,11 +1,12 @@
-package de.danoeh.antennapod.core.tests.util;
+package de.danoeh.antennapod.core.util;
-import android.test.AndroidTestCase;
+import org.junit.Test;
-import de.danoeh.antennapod.core.util.LongIntMap;
+import static org.junit.Assert.assertEquals;
-public class LongLongMapTest extends AndroidTestCase {
+public class LongLongMapTest {
+ @Test
public void testEmptyMap() {
LongIntMap map = new LongIntMap();
assertEquals(0, map.size());
@@ -18,6 +19,7 @@ public class LongLongMapTest extends AndroidTestCase {
assertEquals(1, map.hashCode());
}
+ @Test
public void testSingleElement() {
LongIntMap map = new LongIntMap();
map.put(17, 42);
@@ -30,6 +32,7 @@ public class LongLongMapTest extends AndroidTestCase {
assertEquals(true, map.delete(17));
}
+ @Test
public void testAddAndDelete() {
LongIntMap map = new LongIntMap();
for(int i=0; i < 100; i++) {
@@ -46,6 +49,7 @@ public class LongLongMapTest extends AndroidTestCase {
}
}
+ @Test
public void testOverwrite() {
LongIntMap map = new LongIntMap();
map.put(17, 42);