summaryrefslogtreecommitdiff
path: root/core/src/main/java
diff options
context:
space:
mode:
authorBrad Pitcher <bradpitcher@gmail.com>2019-04-10 08:21:55 -0700
committerGitHub <noreply@github.com>2019-04-10 08:21:55 -0700
commitf2af5192e68d756380556f8420caff2d5095ae97 (patch)
tree66b79c10506df9fafb64e7a68792ee9913ddf005 /core/src/main/java
parent3019c4a9be26239b193537264fbfaca3f8221a1e (diff)
parentd8a010ad487d186afedc85f61a8726813b72a82e (diff)
downloadAntennaPod-f2af5192e68d756380556f8420caff2d5095ae97.zip
Merge branch 'develop' into fix-2359
Diffstat (limited to 'core/src/main/java')
-rw-r--r--core/src/main/java/android/support/v4/app/SafeJobIntentService.java118
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java50
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java2
-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/FeedComponent.java4
-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.java50
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java45
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java109
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java55
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java249
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java98
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java389
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java115
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java89
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java293
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java250
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java490
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java24
-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.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Consumer.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Converter.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java50
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Function.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java37
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Optional.java213
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java109
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java99
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java43
82 files changed, 2052 insertions, 1616 deletions
diff --git a/core/src/main/java/android/support/v4/app/SafeJobIntentService.java b/core/src/main/java/android/support/v4/app/SafeJobIntentService.java
new file mode 100644
index 000000000..c07c409ee
--- /dev/null
+++ b/core/src/main/java/android/support/v4/app/SafeJobIntentService.java
@@ -0,0 +1,118 @@
+package android.support.v4.app;
+
+import android.app.job.JobParameters;
+import android.app.job.JobServiceEngine;
+import android.app.job.JobWorkItem;
+import android.content.Intent;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+
+
+public abstract class SafeJobIntentService extends JobIntentService {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Build.VERSION.SDK_INT >= 26) {
+ mJobImpl = new SafeJobServiceEngineImpl(this);
+ }
+ }
+
+ /**
+ * Implementation of a safe JobServiceEngine for interaction with JobIntentService.
+ */
+ @RequiresApi(26)
+ static final class SafeJobServiceEngineImpl extends JobServiceEngine
+ implements JobIntentService.CompatJobEngine {
+ static final String TAG = "JobServiceEngineImpl";
+
+ static final boolean DEBUG = false;
+
+ final JobIntentService mService;
+ final Object mLock = new Object();
+ JobParameters mParams;
+
+ final class WrapperWorkItem implements JobIntentService.GenericWorkItem {
+ final JobWorkItem mJobWork;
+
+ WrapperWorkItem(JobWorkItem jobWork) {
+ mJobWork = jobWork;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mJobWork.getIntent();
+ }
+
+ @Override
+ public void complete() {
+ synchronized (mLock) {
+ if (mParams != null) {
+ try {
+ mParams.completeWork(mJobWork);
+ } catch (SecurityException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+ }
+ }
+ }
+
+ SafeJobServiceEngineImpl(JobIntentService service) {
+ super(service);
+ mService = service;
+ }
+
+ @Override
+ public IBinder compatGetBinder() {
+ return getBinder();
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (DEBUG) Log.d(TAG, "onStartJob: " + params);
+ mParams = params;
+ // We can now start dequeuing work!
+ mService.ensureProcessorRunningLocked(false);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (DEBUG) Log.d(TAG, "onStartJob: " + params);
+ boolean result = mService.doStopCurrentWork();
+ synchronized (mLock) {
+ // Once we return, the job is stopped, so its JobParameters are no
+ // longer valid and we should not be doing anything with them.
+ mParams = null;
+ }
+ return result;
+ }
+
+ /**
+ * Dequeue some work.
+ */
+ @Override
+ public JobIntentService.GenericWorkItem dequeueWork() {
+ JobWorkItem work = null;
+ synchronized (mLock) {
+ if (mParams == null) {
+ return null;
+ }
+ try {
+ work = mParams.dequeueWork();
+ } catch (SecurityException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+ if (work != null) {
+ work.getIntent().setExtrasClassLoader(mService.getClassLoader());
+ return new WrapperWorkItem(work);
+ } else {
+ return null;
+ }
+ }
+ }
+}
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..1b4cbc0ea 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
@@ -10,21 +10,15 @@ import android.util.Log;
import org.antennapod.audio.MediaPlayer;
-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;
-import de.danoeh.antennapod.core.storage.DBWriter;
/*
* This class's job is do perform maintenance tasks whenever the app has been updated
*/
class UpdateManager {
+ private UpdateManager(){}
+
private static final String TAG = UpdateManager.class.getSimpleName();
private static final String PREF_NAME = "app_version";
@@ -64,32 +58,22 @@ 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 (oldVersionCode < 1050004) {
if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
- UserPreferences.enableSonic(true);
+ UserPreferences.enableSonic();
+ }
+ }
+
+ if (oldVersionCode < 1070196) {
+ // migrate episode cleanup value (unit changed from days to hours)
+ int oldValueInDays = UserPreferences.getEpisodeCleanupValue();
+ if (oldValueInDays > 0) {
+ UserPreferences.setEpisodeCleanupValue(oldValueInDays * 24);
+ } // else 0 or special negative values, no change needed
+ }
+ if (oldVersionCode < 1070197) {
+ if (prefs.getBoolean("prefMobileUpdate", false)) {
+ prefs.edit().putString(UserPreferences.PREF_MOBILE_UPDATE, "everything").apply();
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java
deleted file mode 100644
index 1b4aafeaa..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.danoeh.antennapod.core.asynctask;
-
-import android.content.Context;
-import android.support.v4.content.AsyncTaskLoader;
-
-/**
- * Subclass of AsyncTaskLoader that is made for loading data with one of the DB*-classes.
- * This class will provide a useful default implementation that would otherwise always be necessary when interacting
- * with the DB*-classes with an AsyncTaskLoader.
- */
-abstract class DBTaskLoader<D> extends AsyncTaskLoader<D> {
-
- public DBTaskLoader(Context context) {
- super(context);
- }
-
- @Override
- protected void onStopLoading() {
- super.onStopLoading();
- cancelLoad();
- }
-
- @Override
- protected void onStartLoading() {
- super.onStartLoading();
- // according to https://code.google.com/p/android/issues/detail?id=14944, this has to be called manually
- forceLoad();
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
index 1e0c07b01..4504b2e7f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FeedRemover.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.asynctask;
import android.app.ProgressDialog;
import android.content.Context;
-import android.content.Intent;
import android.os.AsyncTask;
import java.util.concurrent.ExecutionException;
@@ -11,6 +10,7 @@ import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.IntentUtils;
/** Removes a feed in the background. */
public class FeedRemover extends AsyncTask<Void, Void, Void> {
@@ -41,7 +41,7 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
dialog.dismiss();
}
if(skipOnCompletion) {
- context.sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
}
}
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 f4c99011a..318e404c8 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,7 +10,6 @@ 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;
@@ -27,6 +26,7 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
/**
* Performs a click action in a background thread.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
index 420a60469..6d9ab2bd3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java
@@ -19,11 +19,9 @@ import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
public class FlattrStatusFetcher extends Thread {
private static final String TAG = "FlattrStatusFetcher";
- private final Context context;
public FlattrStatusFetcher(Context context) {
super();
- this.context = context;
}
@Override
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/FeedComponent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
index a3f91b1c9..2610d253f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java
@@ -50,7 +50,7 @@ public abstract class FeedComponent {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || !(o instanceof FeedComponent)) return false;
FeedComponent that = (FeedComponent) o;
@@ -62,4 +62,4 @@ public abstract class FeedComponent {
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
-} \ No newline at end of file
+}
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..0f0343f25 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -2,8 +2,8 @@ package de.danoeh.antennapod.core.feed;
import android.database.Cursor;
import android.support.annotation.Nullable;
-
import android.text.TextUtils;
+
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -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;
@@ -192,7 +194,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
if (other.link != null) {
link = other.link;
}
- if (other.pubDate != null && other.pubDate != pubDate) {
+ if (other.pubDate != null && other.pubDate.equals(pubDate)) {
pubDate = other.pubDate;
}
if (other.media != null) {
@@ -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/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
index 200153876..719383d23 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
@@ -8,6 +8,8 @@ import java.util.List;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.LongList;
+import static de.danoeh.antennapod.core.feed.FeedItem.TAG_FAVORITE;
+
public class FeedItemFilter {
private final String[] mProperties;
@@ -19,6 +21,7 @@ public class FeedItemFilter {
private boolean showDownloaded = false;
private boolean showNotDownloaded = false;
private boolean showHasMedia = false;
+ private boolean showIsFavorite = false;
public FeedItemFilter(String properties) {
this(TextUtils.split(properties, ","));
@@ -53,6 +56,9 @@ public class FeedItemFilter {
case "has_media":
showHasMedia = true;
break;
+ case "is_favorite":
+ showIsFavorite = true;
+ break;
}
}
}
@@ -88,6 +94,8 @@ public class FeedItemFilter {
if (showHasMedia && !item.hasMedia()) continue;
+ if (showIsFavorite && !item.isTagged(TAG_FAVORITE)) continue;
+
// If the item reaches here, it meets all criteria
result.add(item);
}
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 a22422596..f3a43e2d0 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
@@ -19,6 +19,7 @@ import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -218,7 +219,7 @@ public class FeedMedia extends FeedFile implements Playable {
* currently being played and the current player status is playing.
*/
public boolean isCurrentlyPlaying() {
- return isPlaying() &&
+ return isPlaying() && PlaybackService.isRunning &&
((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PLAYING));
}
@@ -531,8 +532,8 @@ public class FeedMedia extends FeedFile implements Playable {
UserPreferences.isAutoFlattr() &&
item.getPaymentLink() != null &&
item.getFlattrStatus().getUnflattred() &&
- (completed && autoFlattrThreshold <= 1.0f ||
- played_duration >= autoFlattrThreshold * duration)) {
+ ((completed && autoFlattrThreshold <= 1.0f) ||
+ (played_duration >= autoFlattrThreshold * duration))) {
DBTasks.flattrItemIfLoggedIn(context, item);
}
}
@@ -593,7 +594,7 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public void setDownloaded(boolean downloaded) {
super.setDownloaded(downloaded);
- if(item != null && downloaded) {
+ if(item != null && downloaded && item.isNew()) {
item.setPlayed(false);
}
}
@@ -625,6 +626,9 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
if (FeedMediaFlavorHelper.instanceOfRemoteMedia(o)) {
return o.equals(this);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
index 835dee735..552c1b691 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
@@ -2,31 +2,35 @@ package de.danoeh.antennapod.core.glide;
import android.content.Context;
+import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.Registry;
+import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
-import com.bumptech.glide.module.GlideModule;
+import com.bumptech.glide.module.AppGlideModule;
import java.io.InputStream;
+import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/**
* {@see com.bumptech.glide.integration.okhttp.OkHttpGlideModule}
*/
-public class ApGlideModule implements GlideModule {
+@GlideModule
+public class ApGlideModule extends AppGlideModule {
@Override
- public void applyOptions(Context context, GlideBuilder builder) {
- builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
+ public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
+ builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888));
builder.setDiskCache(new InternalCacheDiskCacheFactory(context,
UserPreferences.getImageCacheSize()));
}
@Override
- public void registerComponents(Context context, Glide glide) {
- glide.register(String.class, InputStream.class, new ApOkHttpUrlLoader.Factory());
+ public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
+ registry.replace(String.class, InputStream.class, new ApOkHttpUrlLoader.Factory());
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java
index fc1acd0e1..d0061af99 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideSettings.java
@@ -6,6 +6,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
* The settings that AntennaPod will use for various Glide options
*/
public class ApGlideSettings {
+ private ApGlideSettings(){}
public static final DiskCacheStrategy AP_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
index 3e4f06a12..2e742e979 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java
@@ -1,12 +1,12 @@
package de.danoeh.antennapod.core.glide;
-import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
@@ -15,14 +15,14 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
+import com.bumptech.glide.load.model.MultiModelLoaderFactory;
+import com.bumptech.glide.signature.ObjectKey;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.service.download.HttpDownloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import okhttp3.Interceptor;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
+import okhttp3.*;
+import okhttp3.internal.http.RealResponseBody;
/**
* @see com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
@@ -56,19 +56,20 @@ class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
- public Factory() {
+ Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*/
- public Factory(OkHttpClient client) {
+ Factory(OkHttpClient client) {
this.client = client;
}
+ @NonNull
@Override
- public ModelLoader<String, InputStream> build(Context context, GenericLoaderFactory factories) {
+ public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new ApOkHttpUrlLoader(client);
}
@@ -84,28 +85,40 @@ class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
this.client = client;
}
+ @Nullable
@Override
- public DataFetcher<InputStream> getResourceFetcher(String model, int width, int height) {
- Log.d(TAG, "getResourceFetcher() called with: " + "model = [" + model + "], width = ["
+ public LoadData<InputStream> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
+ Log.d(TAG, "buildLoadData() called with: " + "model = [" + model + "], width = ["
+ width + "], height = [" + height + "]");
if(TextUtils.isEmpty(model)) {
return null;
} else if(model.startsWith("/")) {
- return new AudioCoverFetcher(model);
+ return new LoadData<>(new ObjectKey(model), new AudioCoverFetcher(model));
} else {
GlideUrl url = new GlideUrl(model);
- return new OkHttpStreamFetcher(client, url);
+ return new LoadData<>(new ObjectKey(model), new OkHttpStreamFetcher(client, url));
}
}
+ @Override
+ public boolean handles(@NonNull String s) {
+ return true;
+ }
+
private static class NetworkAllowanceInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
- if (NetworkUtils.isDownloadAllowed()) {
+ if (NetworkUtils.isImageAllowed()) {
return chain.proceed(chain.request());
} else {
- return null;
+ return new Response.Builder()
+ .protocol(Protocol.HTTP_2)
+ .code(420)
+ .message("Policy Not Fulfilled")
+ .body(ResponseBody.create(null, new byte[0]))
+ .request(chain.request())
+ .build();
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java b/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java
index 8159a1b3e..479846655 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java
@@ -2,7 +2,9 @@ package de.danoeh.antennapod.core.glide;
import android.media.MediaMetadataRetriever;
+import android.support.annotation.NonNull;
import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.data.DataFetcher;
import java.io.ByteArrayInputStream;
@@ -20,22 +22,20 @@ class AudioCoverFetcher implements DataFetcher<InputStream> {
this.path = path;
}
- @Override public String getId() {
- return path;
- }
-
- @Override public InputStream loadData(Priority priority) throws Exception {
+ @Override
+ public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(path);
byte[] picture = retriever.getEmbeddedPicture();
if (picture != null) {
- return new ByteArrayInputStream(picture);
+ callback.onDataReady(new ByteArrayInputStream(picture));
+ return;
}
} finally {
retriever.release();
}
- throw new IOException("Loading embedded cover did not work");
+ callback.onLoadFailed(new IOException("Loading embedded cover did not work"));
}
@Override public void cleanup() {
@@ -44,4 +44,16 @@ class AudioCoverFetcher implements DataFetcher<InputStream> {
@Override public void cancel() {
// cannot cancel
}
+
+ @NonNull
+ @Override
+ public Class<InputStream> getDataClass() {
+ return InputStream.class;
+ }
+
+ @NonNull
+ @Override
+ public DataSource getDataSource() {
+ return DataSource.LOCAL;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java
index ee58c2f39..a740782d6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java
@@ -1,13 +1,15 @@
package de.danoeh.antennapod.core.glide;
-import android.content.Context;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
+import android.support.annotation.NonNull;
import android.util.Log;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import java.security.MessageDigest;
+
public class FastBlurTransformation extends BitmapTransformation {
private static final String TAG = FastBlurTransformation.class.getSimpleName();
@@ -15,8 +17,8 @@ public class FastBlurTransformation extends BitmapTransformation {
private static final int STACK_BLUR_RADIUS = 1;
private static final int BLUR_IMAGE_WIDTH = 150;
- public FastBlurTransformation(Context context) {
- super(context);
+ public FastBlurTransformation() {
+ super();
}
@Override
@@ -33,11 +35,6 @@ public class FastBlurTransformation extends BitmapTransformation {
return result;
}
- @Override
- public String getId() {
- return "FastBlurTransformation[width=" + BLUR_IMAGE_WIDTH + "px,radius=" + STACK_BLUR_RADIUS +"]";
- }
-
private static Bitmap fastBlur(Bitmap bitmap, int radius) {
// Stack Blur v1.0 from
@@ -264,4 +261,8 @@ public class FastBlurTransformation extends BitmapTransformation {
return bitmap;
}
+ @Override
+ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+ messageDigest.update(TAG.getBytes());
+ }
} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
index 3af5e9080..2588cfdee 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
@@ -14,6 +14,7 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
@@ -587,7 +588,7 @@ public class GpodnetService {
String result = null;
ResponseBody body = null;
try {
- String credential = Credentials.basic(username, password);
+ String credential = Credentials.basic(username, password, Charset.forName("UTF-8"));
Request authRequest = request.newBuilder().header("Authorization", credential).build();
Response response = httpClient.newCall(authRequest).execute();
checkStatusCode(response);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
index b76988fd8..330cde525 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
@@ -169,7 +169,7 @@ public class GpodnetEpisodeAction {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || !(o instanceof GpodnetEpisodeAction)) return false;
GpodnetEpisodeAction that = (GpodnetEpisodeAction) o;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
index b51b8d996..5b17dd338 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
@@ -24,6 +24,8 @@ import de.danoeh.antennapod.core.service.GpodnetSyncService;
*/
public class GpodnetPreferences {
+ private GpodnetPreferences(){}
+
private static final String TAG = "GpodnetPreferences";
private static final String PREF_NAME = "gpodder.net";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java
index b7ed890f5..0f3a9fcb3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/SleepTimerPreferences.java
@@ -23,7 +23,6 @@ public class SleepTimerPreferences {
private static final String DEFAULT_VALUE = "15";
private static final int DEFAULT_TIME_UNIT = 1;
- private static Context context;
private static SharedPreferences prefs;
/**
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 44b2fa2b2..805f0c1b6 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
@@ -8,14 +8,7 @@ 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;
@@ -27,6 +20,15 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
+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;
+
/**
* Provides access to preferences set by the user in the settings screen. A
* private instance of this class must first be instantiated via
@@ -34,6 +36,7 @@ import java.util.concurrent.TimeUnit;
* when called.
*/
public class UserPreferences {
+ private UserPreferences(){}
private static final String IMPORT_DIR = "import/";
@@ -44,11 +47,13 @@ public class UserPreferences {
public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
private static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder";
private static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator";
- private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
+ public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
public static final String PREF_COMPACT_NOTIFICATION_BUTTONS = "prefCompactNotificationButtons";
public static final String PREF_LOCKSCREEN_BACKGROUND = "prefLockscreenBackground";
private static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport";
+ public static final String PREF_BACK_BUTTON_BEHAVIOR = "prefBackButtonBehavior";
+ private static final String PREF_BACK_BUTTON_GO_TO_PAGE = "prefBackButtonGoToPage";
// Queue
private static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
@@ -72,7 +77,7 @@ public class UserPreferences {
// Network
private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
- private static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
+ public static final String PREF_MOBILE_UPDATE = "prefMobileUpdateAllowed";
public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
@@ -95,9 +100,13 @@ public class UserPreferences {
// Other
private static final String PREF_DATA_FOLDER = "prefDataFolder";
public static final String PREF_IMAGE_CACHE_SIZE = "prefImageCacheSize";
+ public static final String PREF_DELETE_REMOVES_FROM_QUEUE = "prefDeleteRemovesFromQueue";
// Mediaplayer
+ public static final String PREF_MEDIA_PLAYER = "prefMediaPlayer";
+ public static final String PREF_MEDIA_PLAYER_EXOPLAYER = "exoplayer";
private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ public static final String PREF_PLAYBACK_SKIP_SILENCE = "prefSkipSilence";
private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
private static final String PREF_REWIND_SECS = "prefRewindSecs";
private static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
@@ -107,9 +116,7 @@ public class UserPreferences {
private static final String PREF_RIGHT_VOLUME = "prefRightVolume";
// Experimental
- public static final String PREF_SONIC = "prefSonic";
private static final String PREF_STEREO_TO_MONO = "PrefStereoToMono";
- public static final String PREF_NORMALIZER = "prefNormalizer";
public static final String PREF_CAST_ENABLED = "prefCast"; //Used for enabling Chromecast support
public static final int EPISODE_CLEANUP_QUEUE = -1;
public static final int EPISODE_CLEANUP_NULL = -2;
@@ -122,7 +129,6 @@ public class UserPreferences {
private static final int EPISODE_CACHE_SIZE_UNLIMITED = -1;
public static final int FEED_ORDER_COUNTER = 0;
public static final int FEED_ORDER_ALPHABETICAL = 1;
- public static final int FEED_ORDER_LAST_UPDATE = 2;
public static final int FEED_ORDER_MOST_PLAYED = 3;
public static final int FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM = 0;
public static final int FEED_COUNTER_SHOW_NEW = 1;
@@ -161,6 +167,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;
}
@@ -304,6 +312,10 @@ public class UserPreferences {
return Integer.parseInt(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
}
+ public static boolean shouldDeleteRemoveFromQueue() {
+ return prefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false);
+ }
+
public static boolean isAutoFlattr() {
return prefs.getBoolean(PREF_AUTO_FLATTR, false);
}
@@ -312,6 +324,10 @@ public class UserPreferences {
return prefs.getString(PREF_PLAYBACK_SPEED, "1.00");
}
+ public static boolean isSkipSilence() {
+ return prefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false);
+ }
+
public static String[] getPlaybackSpeedArray() {
return readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
}
@@ -364,8 +380,16 @@ public class UserPreferences {
}
}
+ public static String getMobileUpdatesEnabled() {
+ return prefs.getString(PREF_MOBILE_UPDATE, "images");
+ }
+
public static boolean isAllowMobileUpdate() {
- return prefs.getBoolean(PREF_MOBILE_UPDATE, false);
+ return getMobileUpdatesEnabled().equals("everything");
+ }
+
+ public static boolean isAllowMobileImages() {
+ return isAllowMobileUpdate() || getMobileUpdatesEnabled().equals("images");
}
public static int getParallelDownloads() {
@@ -498,6 +522,12 @@ public class UserPreferences {
.apply();
}
+ public static void setSkipSilence(boolean skipSilence) {
+ prefs.edit()
+ .putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence)
+ .apply();
+ }
+
public static void setPlaybackSpeedArray(String[] speeds) {
JSONArray jsonArray = new JSONArray();
for (String speed : speeds) {
@@ -597,6 +627,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;
}
@@ -636,13 +668,15 @@ public class UserPreferences {
}
public static boolean useSonic() {
- return prefs.getBoolean(PREF_SONIC, false);
+ return prefs.getString(PREF_MEDIA_PLAYER, "sonic").equals("sonic");
}
- public static void enableSonic(boolean enable) {
- prefs.edit()
- .putBoolean(PREF_SONIC, enable)
- .apply();
+ public static boolean useExoplayer() {
+ return prefs.getString(PREF_MEDIA_PLAYER, "sonic").equals(PREF_MEDIA_PLAYER_EXOPLAYER);
+ }
+
+ public static void enableSonic() {
+ prefs.edit().putString(PREF_MEDIA_PLAYER, "sonic").apply();
}
public static boolean stereoToMono() {
@@ -665,7 +699,7 @@ public class UserPreferences {
}
public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
- int cleanupValue = Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
+ int cleanupValue = getEpisodeCleanupValue();
if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
return new APQueueCleanupAlgorithm();
} else if (cleanupValue == EPISODE_CLEANUP_NULL) {
@@ -675,6 +709,16 @@ public class UserPreferences {
}
}
+ public static int getEpisodeCleanupValue() {
+ return Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
+ }
+
+ public static void setEpisodeCleanupValue(int episodeCleanupValue) {
+ prefs.edit()
+ .putString(PREF_EPISODE_CLEANUP, Integer.toString(episodeCleanupValue))
+ .apply();
+ }
+
/**
* Return the folder where the app stores all of its data. This method will
* return the standard data folder if none has been set by the user.
@@ -802,4 +846,29 @@ public class UserPreferences {
public enum VideoBackgroundBehavior {
STOP, PICTURE_IN_PICTURE, CONTINUE_PLAYING
}
+
+ public enum BackButtonBehavior {
+ DEFAULT, OPEN_DRAWER, DOUBLE_TAP, SHOW_PROMPT, GO_TO_PAGE
+ }
+
+ public static BackButtonBehavior getBackButtonBehavior() {
+ switch (prefs.getString(PREF_BACK_BUTTON_BEHAVIOR, "default")) {
+ case "default": return BackButtonBehavior.DEFAULT;
+ case "drawer": return BackButtonBehavior.OPEN_DRAWER;
+ case "doubletap": return BackButtonBehavior.DOUBLE_TAP;
+ case "prompt": return BackButtonBehavior.SHOW_PROMPT;
+ case "page": return BackButtonBehavior.GO_TO_PAGE;
+ default: return BackButtonBehavior.DEFAULT;
+ }
+ }
+
+ public static String getBackButtonGoToPage() {
+ return prefs.getString(PREF_BACK_BUTTON_GO_TO_PAGE, "QueueFragment");
+ }
+
+ public static void setBackButtonGoToPage(String tag) {
+ prefs.edit()
+ .putString(PREF_BACK_BUTTON_GO_TO_PAGE, tag)
+ .apply();
+ }
}
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
index edc2ea3e0..7663cdbe4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
@@ -6,10 +6,11 @@ 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;
+import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
+
public class PlayerWidget extends AppWidgetProvider {
private static final String TAG = "PlayerWidget";
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
index 55a8d6b86..7d80d4e7c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
@@ -5,6 +5,7 @@ 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;
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 de040603d..5584991ca 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
@@ -6,8 +6,8 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
-import android.support.v4.app.JobIntentService;
import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.SafeJobIntentService;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -44,7 +44,8 @@ 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 JobIntentService {
+public class GpodnetSyncService extends SafeJobIntentService {
+
private static final String TAG = "GpodnetSyncService";
private static final long WAIT_INTERVAL = 5000L;
@@ -61,8 +62,10 @@ public class GpodnetSyncService extends JobIntentService {
private static boolean syncSubscriptions = false;
private static boolean syncActions = false;
+ private static final int JOB_ID = -17000;
+
private static void enqueueWork(Context context, Intent intent) {
- enqueueWork(context, GpodnetSyncService.class, 0, intent);
+ enqueueWork(context, GpodnetSyncService.class, JOB_ID, intent);
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
index 4948912ca..dbf80cf37 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
@@ -10,7 +10,7 @@ import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.NonNull;
-import android.support.v4.app.JobIntentService;
+import android.support.v4.app.SafeJobIntentService;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -21,23 +21,26 @@ import com.bumptech.glide.Glide;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.receiver.PlayerWidget;
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 {
+public class PlayerWidgetJobService extends SafeJobIntentService {
+
private static final String TAG = "PlayerWidgetJobService";
private PlaybackService playbackService;
private final Object waitForService = new Object();
+ private static final int JOB_ID = -17001;
+
public static void updateWidget(Context context) {
- enqueueWork(context, PlayerWidgetJobService.class, 0, new Intent(context, PlayerWidgetJobService.class));
+ enqueueWork(context, PlayerWidgetJobService.class, JOB_ID, new Intent(context, PlayerWidgetJobService.class));
}
@Override
@@ -46,8 +49,8 @@ public class PlayerWidgetJobService extends JobIntentService {
return;
}
- if (PlaybackService.isRunning && playbackService == null) {
- synchronized (waitForService) {
+ synchronized (waitForService) {
+ if (PlaybackService.isRunning && playbackService == null) {
bindService(new Intent(this, PlaybackService.class), mConnection, 0);
while (playbackService == null) {
try {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index 57d099dfc..97007a214 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
@@ -40,6 +40,9 @@ import okhttp3.internal.http.StatusLine;
* Provides access to a HttpClient singleton.
*/
public class AntennapodHttpClient {
+
+ private AntennapodHttpClient(){}
+
private static final String TAG = "AntennapodHttpClient";
private static final int CONNECTION_TIMEOUT = 30000;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
index 9a64bebf7..48234c387 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java
@@ -124,7 +124,7 @@ public class DownloadRequest implements Parcelable {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || !(o instanceof DownloadRequest)) return false;
DownloadRequest that = (DownloadRequest) o;
@@ -211,10 +211,6 @@ public class DownloadRequest implements Parcelable {
this.size = size;
}
- public int getStatusMsg() {
- return statusMsg;
- }
-
public void setStatusMsg(int statusMsg) {
this.statusMsg = statusMsg;
}
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 9c2266622..ae1e9de86 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
@@ -13,14 +13,12 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.webkit.URLUtil;
-import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.io.FileUtils;
import org.xml.sax.SAXException;
@@ -30,10 +28,8 @@ import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
@@ -55,7 +51,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;
@@ -75,6 +70,7 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.InvalidFeedException;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.greenrobot.event.EventBus;
/**
@@ -117,11 +113,6 @@ public class DownloadService extends Service {
private CompletionService<Downloader> downloadExecutor;
private FeedSyncThread feedSyncThread;
- /**
- * Number of threads of downloadExecutor.
- */
- private static final int NUM_PARALLEL_DOWNLOADS = 6;
-
private DownloadRequester requester;
@@ -489,9 +480,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++;
}
}
@@ -688,10 +677,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 {
@@ -866,22 +851,6 @@ public class DownloadService extends Service {
return true;
}
- /**
- * Delete files that aren't needed anymore
- */
- private void cleanup(Feed feed) {
- if (feed.getFile_url() != null) {
- if (new File(feed.getFile_url()).delete()) {
- Log.d(TAG, "Successfully deleted cache file.");
- } else {
- Log.e(TAG, "Failed to delete cache file.");
- }
- feed.setFile_url(null);
- } else {
- Log.d(TAG, "Didn't delete cache file: File url is not set.");
- }
- }
-
public void shutdown() {
isActive = false;
if (isCollectingRequests) {
@@ -1101,26 +1070,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
- 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/Downloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
index 445210d3a..38b93eab8 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.service.download;
import android.content.Context;
import android.net.wifi.WifiManager;
+import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
@@ -18,10 +19,12 @@ public abstract class Downloader implements Callable<Downloader> {
volatile boolean cancelled;
+ @NonNull
final DownloadRequest request;
+ @NonNull
final DownloadStatus result;
- Downloader(DownloadRequest request) {
+ Downloader(@NonNull DownloadRequest request) {
super();
this.request = request;
this.request.setStatusMsg(R.string.download_pending);
@@ -54,10 +57,12 @@ public abstract class Downloader implements Callable<Downloader> {
return this;
}
+ @NonNull
public DownloadRequest getDownloadRequest() {
return request;
}
+ @NonNull
public DownloadStatus getResult() {
return result;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java
deleted file mode 100644
index b0829f084..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloaderCallback.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.danoeh.antennapod.core.service.download;
-
-/**
- * Callback used by the Downloader-classes to notify the requester that the
- * download has completed.
- */
-public interface DownloaderCallback {
-
- void onDownloadCompleted(Downloader downloader);
-}
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..c27cefc10 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
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.service.download;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
@@ -20,7 +21,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;
@@ -39,7 +39,7 @@ public class HttpDownloader extends Downloader {
private static final int BUFFER_SIZE = 8 * 1024;
- public HttpDownloader(DownloadRequest request) {
+ public HttpDownloader(@NonNull DownloadRequest request) {
super(request);
}
@@ -50,13 +50,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();
@@ -310,7 +305,7 @@ public class HttpDownloader extends Downloader {
String encoded = ByteString.of(bytes).base64();
return "Basic " + encoded;
} catch (UnsupportedEncodingException e) {
- throw new AssertionError();
+ throw new AssertionError(e);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
new file mode 100644
index 000000000..281bd064b
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
@@ -0,0 +1,249 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.SurfaceHolder;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.DefaultLoadControl;
+import com.google.android.exoplayer2.DefaultRenderersFactory;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SeekParameters;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.audio.AudioAttributes;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
+
+import org.antennapod.audio.MediaPlayer;
+import de.danoeh.antennapod.core.util.playback.IPlayer;
+
+
+public class ExoPlayerWrapper implements IPlayer {
+ private final Context mContext;
+ private SimpleExoPlayer mExoPlayer;
+ private MediaSource mediaSource;
+ private MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener;
+ private MediaPlayer.OnCompletionListener audioCompletionListener;
+ private MediaPlayer.OnErrorListener audioErrorListener;
+
+ ExoPlayerWrapper(Context context) {
+ mContext = context;
+ mExoPlayer = createPlayer();
+ }
+
+ private SimpleExoPlayer createPlayer() {
+ SimpleExoPlayer p = ExoPlayerFactory.newSimpleInstance(mContext, new DefaultRenderersFactory(mContext),
+ new DefaultTrackSelector(), new DefaultLoadControl());
+ p.setSeekParameters(SeekParameters.PREVIOUS_SYNC);
+ p.addListener(new Player.EventListener() {
+ @Override
+ public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
+
+ }
+
+ @Override
+ public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+
+ }
+
+ @Override
+ public void onLoadingChanged(boolean isLoading) {
+
+ }
+
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ if (playbackState == Player.STATE_ENDED) {
+ audioCompletionListener.onCompletion(null);
+ }
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+
+ }
+
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+
+ }
+
+ @Override
+ public void onPlayerError(ExoPlaybackException error) {
+ if (audioErrorListener != null) {
+ audioErrorListener.onError(null, 0, 0);
+ }
+ }
+
+ @Override
+ public void onPositionDiscontinuity(int reason) {
+
+ }
+
+ @Override
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+
+ }
+
+ @Override
+ public void onSeekProcessed() {
+ audioSeekCompleteListener.onSeekComplete(null);
+ }
+ });
+ return p;
+ }
+
+ @Override
+ public boolean canSetSpeed() {
+ return true;
+ }
+
+ @Override
+ public boolean canDownmix() {
+ return false;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ return (int) mExoPlayer.getCurrentPosition();
+ }
+
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ return mExoPlayer.getPlaybackParameters().speed;
+ }
+
+ @Override
+ public int getDuration() {
+ if (mExoPlayer.getDuration() == C.TIME_UNSET) {
+ return PlaybackServiceMediaPlayer.INVALID_TIME;
+ }
+ return (int) mExoPlayer.getDuration();
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mExoPlayer.getPlayWhenReady();
+ }
+
+ @Override
+ public void pause() {
+ mExoPlayer.setPlayWhenReady(false);
+ }
+
+ @Override
+ public void prepare() throws IllegalStateException {
+ mExoPlayer.prepare(mediaSource);
+ }
+
+ @Override
+ public void release() {
+ if (mExoPlayer != null) {
+ mExoPlayer.release();
+ }
+ audioSeekCompleteListener = null;
+ audioCompletionListener = null;
+ audioErrorListener = null;
+ }
+
+ @Override
+ public void reset() {
+ mExoPlayer.release();
+ mExoPlayer = createPlayer();
+ }
+
+ @Override
+ public void seekTo(int i) throws IllegalStateException {
+ mExoPlayer.seekTo(i);
+ }
+
+ @Override
+ public void setAudioStreamType(int i) {
+ AudioAttributes a = mExoPlayer.getAudioAttributes();
+ AudioAttributes.Builder b = new AudioAttributes.Builder();
+ b.setContentType(i);
+ b.setFlags(a.flags);
+ b.setUsage(a.usage);
+ mExoPlayer.setAudioAttributes(b.build());
+ }
+
+ @Override
+ public void setDataSource(String s) throws IllegalArgumentException, IllegalStateException {
+ DataSource.Factory dataSourceFactory =
+ new DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, mContext.getPackageName()), null);
+ ExtractorMediaSource.Factory f = new ExtractorMediaSource.Factory(dataSourceFactory);
+ mediaSource = f.createMediaSource(Uri.parse(s));
+ }
+
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ mExoPlayer.setVideoSurfaceHolder(sh);
+ }
+
+ @Override
+ public void setPlaybackParams(float speed, boolean skipSilence) {
+ PlaybackParameters params = mExoPlayer.getPlaybackParameters();
+ mExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, params.pitch, skipSilence));
+ }
+
+ @Override
+ public void setDownmix(boolean b) {
+
+ }
+
+ @Override
+ public void setVolume(float v, float v1) {
+ mExoPlayer.setVolume(v);
+ }
+
+ @Override
+ public void setWakeMode(Context context, int i) {
+
+ }
+
+ @Override
+ public void start() {
+ mExoPlayer.setPlayWhenReady(true);
+ }
+
+ @Override
+ public void stop() {
+ mExoPlayer.stop();
+ }
+
+ void setOnCompletionListener(MediaPlayer.OnCompletionListener audioCompletionListener) {
+ this.audioCompletionListener = audioCompletionListener;
+ }
+
+ void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener) {
+ this.audioSeekCompleteListener = audioSeekCompleteListener;
+ }
+
+ void setOnErrorListener(MediaPlayer.OnErrorListener audioErrorListener) {
+ this.audioErrorListener = audioErrorListener;
+ }
+
+ int getVideoWidth() {
+ if (mExoPlayer.getVideoFormat() == null) {
+ return 0;
+ }
+ return mExoPlayer.getVideoFormat().width;
+ }
+
+ int getVideoHeight() {
+ if (mExoPlayer.getVideoFormat() == null) {
+ return 0;
+ }
+ return mExoPlayer.getVideoFormat().height;
+ }
+}
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 0e64f484f..9274b9a49 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
@@ -1,7 +1,10 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
import android.media.AudioManager;
+import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.telephony.TelephonyManager;
@@ -202,9 +205,26 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
private void resumeSync() {
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- int focusGained = audioManager.requestAudioFocus(
- audioFocusChangeListener, AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
+ int focusGained;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ .build();
+ AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(audioAttributes)
+ .setOnAudioFocusChangeListener(audioFocusChangeListener)
+ .setAcceptsDelayedFocusGain(true)
+ .setWillPauseWhenDucked(true)
+ .build();
+ focusGained = audioManager.requestAudioFocus(audioFocusRequest);
+ } else {
+ focusGained = audioManager.requestAudioFocus(
+ audioFocusChangeListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ }
+
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audiofocus successfully requested");
Log.d(TAG, "Resuming/Starting playback");
@@ -216,7 +236,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
Log.e(TAG, Log.getStackTraceString(e));
UserPreferences.setPlaybackSpeed(String.valueOf(speed));
}
- setSpeed(speed);
+ setPlaybackParams(speed, UserPreferences.isSkipSilence());
setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume());
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
@@ -259,7 +279,13 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.PAUSED, media, getPosition());
if (abandonFocus) {
- audioManager.abandonAudioFocus(audioFocusChangeListener);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ AudioFocusRequest.Builder builder = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setOnAudioFocusChangeListener(audioFocusChangeListener);
+ audioManager.abandonAudioFocusRequest(builder.build());
+ } else {
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ }
pausedBecauseOfTransientAudiofocusLoss = false;
}
if (stream && reinit) {
@@ -313,7 +339,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
Log.d(TAG, "Resource prepared");
- if (mediaType == MediaType.VIDEO) {
+ if (mediaType == MediaType.VIDEO && mediaPlayer instanceof ExoPlayerWrapper) {
+ ExoPlayerWrapper vp = (ExoPlayerWrapper) mediaPlayer;
+ videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
+ } else if(mediaType == MediaType.VIDEO && mediaPlayer instanceof VideoPlayer) {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
}
@@ -447,7 +476,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
retVal = mediaPlayer.getDuration();
- } else if (media != null && media.getDuration() > 0) {
+ }
+ if (retVal <= 0 && media != null && media.getDuration() > 0) {
retVal = media.getDuration();
}
@@ -507,14 +537,14 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
* Sets the playback speed.
* This method is executed on the caller's thread.
*/
- private void setSpeedSync(float speed) {
+ private void setSpeedSyncAndSkipSilence(float speed, boolean skipSilence) {
playerLock.lock();
if (media != null && media.getMediaType() == MediaType.AUDIO) {
if (mediaPlayer.canSetSpeed()) {
- mediaPlayer.setPlaybackSpeed(speed);
Log.d(TAG, "Playback speed was set to " + speed);
callback.playbackSpeedChanged(speed);
}
+ mediaPlayer.setPlaybackParams(speed, skipSilence);
}
playerLock.unlock();
}
@@ -524,8 +554,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
* This method is executed on an internal executor service.
*/
@Override
- public void setSpeed(final float speed) {
- executor.submit(() -> setSpeedSync(speed));
+ public void setPlaybackParams(final float speed, final boolean skipSilence) {
+ executor.submit(() -> setSpeedSyncAndSkipSilence(speed, skipSilence));
}
/**
@@ -610,13 +640,29 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
executor.shutdown();
if (mediaPlayer != null) {
try {
- mediaPlayer.stop();
+ removeMediaPlayerErrorListener();
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.stop();
+ }
} catch (Exception ignore) { }
mediaPlayer.release();
}
releaseWifiLockIfNecessary();
}
+ private void removeMediaPlayerErrorListener() {
+ if (mediaPlayer instanceof VideoPlayer) {
+ VideoPlayer vp = (VideoPlayer) mediaPlayer;
+ vp.setOnErrorListener((mp, what, extra) -> true);
+ } else if (mediaPlayer instanceof AudioPlayer) {
+ AudioPlayer ap = (AudioPlayer) mediaPlayer;
+ ap.setOnErrorListener((mediaPlayer, i, i1) -> true);
+ } else if (mediaPlayer instanceof ExoPlayerWrapper) {
+ ExoPlayerWrapper ap = (ExoPlayerWrapper) mediaPlayer;
+ ap.setOnErrorListener((mediaPlayer, i, i1) -> true);
+ }
+ }
+
/**
* Releases internally used resources. This method should only be called when the object is not used anymore.
* This method is executed on an internal executor service.
@@ -669,6 +715,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
Pair<Integer, Integer> res;
if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
res = null;
+ } else if (mediaPlayer instanceof ExoPlayerWrapper) {
+ ExoPlayerWrapper vp = (ExoPlayerWrapper) mediaPlayer;
+ videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
+ res = videoSize;
} else {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
@@ -698,15 +748,19 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (mediaPlayer != null) {
mediaPlayer.release();
}
- if(media == null) {
+ if (media == null) {
mediaPlayer = null;
return;
}
- if (media.getMediaType() == MediaType.VIDEO) {
+
+ if (UserPreferences.useExoplayer()) {
+ mediaPlayer = new ExoPlayerWrapper(context);
+ } else if (media.getMediaType() == MediaType.VIDEO) {
mediaPlayer = new VideoPlayer();
} else {
mediaPlayer = new AudioPlayer(context);
}
+
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
setMediaPlayerListeners(mediaPlayer);
@@ -787,7 +841,14 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (mediaPlayer != null) {
mediaPlayer.reset();
}
- audioManager.abandonAudioFocus(audioFocusChangeListener);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ AudioFocusRequest.Builder builder = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setOnAudioFocusChangeListener(audioFocusChangeListener);
+ audioManager.abandonAudioFocusRequest(builder.build());
+ } else {
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ }
final Playable currentMedia = media;
Playable nextMedia = null;
@@ -883,6 +944,11 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
ap.setOnBufferingUpdateListener(audioBufferingUpdateListener);
ap.setOnInfoListener(audioInfoListener);
ap.setOnSpeedAdjustmentAvailableChangedListener(audioSetSpeedAbilityListener);
+ } else if (mp instanceof ExoPlayerWrapper) {
+ ExoPlayerWrapper ap = (ExoPlayerWrapper) mp;
+ ap.setOnCompletionListener(audioCompletionListener);
+ ap.setOnSeekCompleteListener(audioSeekCompleteListener);
+ ap.setOnErrorListener(audioErrorListener);
} else {
Log.w(TAG, "Unknown media player: " + mp);
}
@@ -925,7 +991,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
private final MediaPlayer.OnErrorListener audioErrorListener =
(mp, what, extra) -> {
- if(mp.canFallback()) {
+ if(mp != null && mp.canFallback()) {
mp.fallback();
return true;
} else {
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 be6cb346d..7fe93a162 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
@@ -24,13 +24,14 @@ import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.ContextCompat;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
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.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -39,6 +40,7 @@ import android.view.SurfaceHolder;
import android.widget.Toast;
import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import java.util.ArrayList;
@@ -65,14 +67,20 @@ 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.IntentUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.greenrobot.event.EventBus;
/**
* Controls the MediaPlayer that plays a FeedMedia-file
+ *
+ * Callers should connect to the service with either:
+ * - .bindService()
+ * - ContextCompat.startForegroundService(), optionally with arguments, such as media to be played, in intent extras
+ *
*/
public class PlaybackService extends MediaBrowserServiceCompat {
/**
@@ -191,10 +199,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
public static boolean isRunning = false;
/**
- * Is true if service has received a valid start command.
- */
- public static boolean started = false;
- /**
* Is true if the service was running, but paused due to headphone disconnect
*/
private static boolean transientPause = false;
@@ -310,15 +314,9 @@ 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() {
@@ -343,8 +341,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service is about to be destroyed");
+ stopForeground(true);
isRunning = false;
- started = false;
currentMediaType = MediaType.UNKNOWN;
PreferenceManager.getDefaultSharedPreferences(this)
@@ -365,7 +363,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
mediaPlayer.shutdown();
taskManager.shutdown();
}
-
+
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName +
@@ -459,33 +457,32 @@ public class PlaybackService extends MediaBrowserServiceCompat {
final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false);
Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
if (keycode == -1 && playable == null && !castDisconnect) {
- Log.e(TAG, "PlaybackService was started with no arguments");
- stopSelf();
+ // Typical cases when the service was started with no argument
+ // - when it is first bound, and then moved to startedState, as in <code>serviceManager.moveServiceToStartedState()</code>
+ // - callers (e.g., Controller) explicitly
+ Log.d(TAG, "PlaybackService was started with no arguments.");
return Service.START_NOT_STICKY;
}
- if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
- Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
- stopForeground(true);
- } else {
-
- if (keycode != -1) {
- Log.d(TAG, "Received media button event");
- handleKeycode(keycode, true);
- } else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
- started = true;
- boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
- true);
- boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
- boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- //If the user asks to play External Media, the casting session, if on, should end.
- flavorHelper.castDisconnect(playable instanceof ExternalMedia);
- if (playable instanceof FeedMedia) {
- playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
- }
- mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
+ if (keycode != -1) {
+ Log.d(TAG, "Received media button event");
+ boolean handled = handleKeycode(keycode, true);
+ if (!handled) {
+ // Just silently ignores unsupported keycode. Whether the service will
+ // continue to run is solely dependent on whether it is playing some media.
+ return Service.START_NOT_STICKY;
+ }
+ } else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
+ boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true);
+ boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
+ boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ //If the user asks to play External Media, the casting session, if on, should end.
+ flavorHelper.castDisconnect(playable instanceof ExternalMedia);
+ if (playable instanceof FeedMedia) {
+ playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
}
+ mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
return Service.START_NOT_STICKY;
@@ -559,12 +556,23 @@ public class PlaybackService extends MediaBrowserServiceCompat {
mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
+ // The logic gives UI illusion of stop by removing notification
+ // In the UI within AntennaPod, including widgets, it is seen as PAUSE, e.g.,
+ // users can still user on-screen widget to resume playing.
if (status == PlayerStatus.PLAYING) {
+ // Implementation note: Use of a state in serviceManager to tell it to
+ // show stop state UI (i.e., stopForeground(true)) is a bit awkward.
+ //
+ // More intuitive API would be for mediaPlayer.pause() returns a Future that
+ // returns after pause, including the related async notification work completes.
+ // However, it has its own complication, that mediaPlayer.pause() does not
+ // really know when all the related work completes, as they are buried into
+ // (asynchronous) callbacks.
+ serviceManager.treatNextPauseAsStopOnUI();
mediaPlayer.pause(true, true);
- started = false;
+ } else {
+ serviceManager.showUIForStopState();
}
-
- stopForeground(true); // gets rid of persistent notification
return true;
default:
Log.d(TAG, "Unhandled key code: " + keycode);
@@ -580,7 +588,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
if (playable != null) {
mediaPlayer.playMediaObject(playable, false, true, true);
- started = true;
PlaybackService.this.updateMediaSessionMetadata(playable);
}
}
@@ -594,19 +601,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
mediaPlayer.setVideoSurface(sh);
}
- /**
- * Called when the surface holder of the mediaplayer has to be changed.
- */
- private void resetVideoSurface() {
- taskManager.cancelPositionSaver();
- mediaPlayer.resetVideoSurface();
- }
-
public void notifyVideoSurfaceAbandoned() {
+ Log.v(TAG, "notifyVideoSurfaceAbandoned()");
mediaPlayer.pause(true, false);
mediaPlayer.resetVideoSurface();
- setupNotification(getPlayable());
- stopForeground(!UserPreferences.isPersistNotify());
}
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@@ -652,7 +650,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() {
@Override
public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
- currentMediaType = mediaPlayer.getCurrentMediaType();
+ if (mediaPlayer != null) {
+ currentMediaType = mediaPlayer.getCurrentMediaType();
+ } else {
+ currentMediaType = MediaType.UNKNOWN;
+ }
+
updateMediaSession(newInfo.playerStatus);
switch (newInfo.playerStatus) {
case INITIALIZED:
@@ -664,27 +667,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
break;
case PAUSED:
- if ((UserPreferences.isPersistNotify() || isCasting) &&
- android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- // do not remove notification on pause based on user pref and whether android version supports expanded notifications
- // Change [Play] button to [Pause]
- setupNotification(newInfo);
- } else if (!UserPreferences.isPersistNotify() && !isCasting) {
- // remove notification on pause
- stopForeground(true);
- }
writePlayerStatusPlaybackPreferences();
break;
case STOPPED:
- //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- //stopSelf();
+ //writePlaybackPreferencesNoMediaPlaying();
break;
case PLAYING:
writePlayerStatusPlaybackPreferences();
- setupNotification(newInfo);
- started = true;
// set sleep timer if auto-enabled
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
@@ -699,9 +690,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);
+ IntentUtils.sendLocalBroadcast(getApplicationContext(), ACTION_PLAYER_STATUS_CHANGED);
PlayerWidgetJobService.updateWidget(getBaseContext());
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
@@ -709,7 +698,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void shouldStop() {
- stopSelf();
+ serviceManager.stopService();
}
@Override
@@ -758,7 +747,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
writePlaybackPreferencesNoMediaPlaying();
- stopSelf();
return true;
}
@@ -842,9 +830,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (stopPlaying) {
taskManager.cancelPositionSaver();
writePlaybackPreferencesNoMediaPlaying();
- if (!isCasting) {
- stopForeground(true);
- }
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
@@ -913,7 +898,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
final List<FeedItem> queue = taskManager.getQueue();
if (QueueAccess.ItemListAccess(queue).contains(item.getId())) {
// don't know if it actually matters to not autodownload when smart mark as played is triggered
- DBWriter.removeQueueItem(PlaybackService.this, item, ended);
+ DBWriter.removeQueueItem(PlaybackService.this, ended, item);
}
} catch (InterruptedException e) {
e.printStackTrace();
@@ -1042,17 +1027,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
editor.commit();
}
- /**
- * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute.
- */
- private void postStatusUpdateIntent() {
- sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED));
- }
-
private void sendNotificationBroadcast(int type, int code) {
Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION);
intent.putExtra(EXTRA_NOTIFICATION_TYPE, type);
intent.putExtra(EXTRA_NOTIFICATION_CODE, code);
+ intent.setPackage(getPackageName());
sendBroadcast(intent);
}
@@ -1064,6 +1043,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private void updateMediaSession(final PlayerStatus playerStatus) {
PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder();
+ @PlaybackStateCompat.State
int state;
if (playerStatus != null) {
switch (playerStatus) {
@@ -1098,7 +1078,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else {
state = PlaybackStateCompat.STATE_NONE;
}
- sessionState.setState(state, mediaPlayer.getPosition(), mediaPlayer.getPlaybackSpeed());
+ sessionState.setState(state, getCurrentPosition(), getCurrentPlaybackSpeed());
long capabilities = PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_REWIND
| PlaybackStateCompat.ACTION_FAST_FORWARD
@@ -1127,7 +1107,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
flavorHelper.mediaSessionSetExtraForWear(mediaSession);
- mediaSession.setPlaybackState(sessionState.build());
+ final PlaybackStateCompat sessionStateBuilt = sessionState.build();
+ mediaSession.setPlaybackState(sessionStateBuilt);
+ serviceManager.onPlaybackStateChange(sessionStateBuilt);
}
private static boolean useSkipToPreviousForRewindInLockscreen() {
@@ -1167,10 +1149,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, imageLocation);
try {
Bitmap art = Glide.with(this)
- .load(imageLocation)
.asBitmap()
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .load(imageLocation)
+ .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
+ .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.get();
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art);
} catch (Throwable tr) {
@@ -1181,7 +1163,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, imageLocation);
}
}
- if (!Thread.currentThread().isInterrupted() && started) {
+ if (!Thread.currentThread().isInterrupted() && isStarted()) {
mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT));
@@ -1204,49 +1186,55 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
private Thread notificationSetupThread;
- /**
- * Prepares notification and starts the service in the foreground.
- */
- private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
- setupNotification(info.playable);
- }
-
- private synchronized void setupNotification(final Playable playable) {
+ private synchronized void setupNotification(final Playable playable, boolean treatPauseAsStop) {
if (notificationSetupThread != null) {
notificationSetupThread.interrupt();
}
+ if (playable == null) {
+ Log.d(TAG, "setupNotification: playable is null");
+ if (!isStarted()) {
+ serviceManager.stopService();
+ }
+ return;
+ }
Runnable notificationSetupTask = new Runnable() {
Bitmap icon = null;
@Override
public void run() {
- Log.d(TAG, "Starting background work");
- if (playable != null) {
- int iconSize = getResources().getDimensionPixelSize(
- android.R.dimen.notification_large_icon_width);
- try {
- icon = Glide.with(PlaybackService.this)
- .load(playable.getImageLocation())
- .asBitmap()
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .centerCrop()
- .into(iconSize, iconSize)
- .get();
- } catch (Throwable tr) {
- Log.e(TAG, "Error loading the media icon for the notification", tr);
+ Log.d(TAG, "notificationSetupTask: Starting background work");
+
+ if (mediaPlayer == null) {
+ Log.d(TAG, "notificationSetupTask: mediaPlayer is null");
+ if (!isStarted()) {
+ serviceManager.stopService();
}
+ return;
+ }
+
+ int iconSize = getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width);
+ try {
+ icon = Glide.with(PlaybackService.this)
+ .asBitmap()
+ .load(playable.getImageLocation())
+ .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
+ .apply(new RequestOptions().centerCrop())
+ .submit(iconSize, iconSize)
+ .get();
+ } catch (Throwable tr) {
+ Log.e(TAG, "Error loading the media icon for the notification", tr);
}
+
if (icon == null) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()));
}
- if (mediaPlayer == null) {
- return;
- }
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
+ Log.v(TAG, "notificationSetupTask: playerStatus=" + playerStatus);
- if (!Thread.currentThread().isInterrupted() && started && playable != null) {
+ if (!Thread.currentThread().isInterrupted() && isStarted()) {
String contentText = playable.getEpisodeTitle();
String contentTitle = playable.getFeedTitle();
Notification notification;
@@ -1338,15 +1326,33 @@ public class PlaybackService extends MediaBrowserServiceCompat {
playerStatus == PlayerStatus.PREPARING ||
playerStatus == PlayerStatus.SEEKING ||
isCasting) {
+ Log.v(TAG, "notificationSetupTask: make service foreground");
startForeground(NOTIFICATION_ID, notification);
+ } else if (playerStatus == PlayerStatus.PAUSED) {
+ if (treatPauseAsStop) {
+ stopForeground(true);
+ } else if ((UserPreferences.isPersistNotify() || isCasting) &&
+ android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // do not remove notification on pause based on user pref and whether android version supports expanded notifications
+ // Change [Play] button to [Pause]
+ leaveNotificationAsBackground(notification);
+ } else if (!UserPreferences.isPersistNotify() && !isCasting) {
+ // remove notification on pause
+ stopForeground(true);
+ }
} else {
- stopForeground(false);
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- mNotificationManager.notify(NOTIFICATION_ID, notification);
+ leaveNotificationAsBackground(notification);
}
Log.d(TAG, "Notification set up");
}
}
+
+ private void leaveNotificationAsBackground(@NonNull Notification notification) {
+ stopForeground(false);
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mNotificationManager.notify(NOTIFICATION_ID, notification);
+ }
+
};
notificationSetupThread = new Thread(notificationSetupTask);
notificationSetupThread.start();
@@ -1541,7 +1547,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- stopSelf();
+ serviceManager.stopService();
}
}
@@ -1618,7 +1624,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void setSpeed(float speed) {
- mediaPlayer.setSpeed(speed);
+ mediaPlayer.setPlaybackParams(speed, UserPreferences.isSkipSilence());
+ }
+
+ public void skipSilence(boolean skipSilence) {
+ mediaPlayer.setPlaybackParams(getCurrentPlaybackSpeed(), skipSilence);
}
public void setVolume(float leftVolume, float rightVolume) {
@@ -1626,6 +1636,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public float getCurrentPlaybackSpeed() {
+ if(mediaPlayer == null) {
+ return 1.0f;
+ }
return mediaPlayer.getPlaybackSpeed();
}
@@ -1667,6 +1680,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* an invalid state.
*/
public int getDuration() {
+ if (mediaPlayer == null) {
+ return INVALID_TIME;
+ }
return mediaPlayer.getDuration();
}
@@ -1675,6 +1691,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* is in an invalid state.
*/
public int getCurrentPosition() {
+ if (mediaPlayer == null) {
+ return INVALID_TIME;
+ }
return mediaPlayer.getPosition();
}
@@ -1783,7 +1802,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public boolean onMediaButtonEvent(final Intent mediaButton) {
Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
if (mediaButton != null) {
- KeyEvent keyEvent = (KeyEvent) mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ KeyEvent keyEvent = mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent != null &&
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
keyEvent.getRepeatCount() == 0) {
@@ -1826,8 +1845,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position);
- void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
-
MediaSessionCompat getMediaSession();
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
@@ -1867,24 +1884,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info) {
- if (connected) {
- PlaybackService.this.setupNotification(info);
- } else {
- PlayerStatus status = info.playerStatus;
- if ((status == PlayerStatus.PLAYING ||
- status == PlayerStatus.SEEKING ||
- status == PlayerStatus.PREPARING ||
- UserPreferences.isPersistNotify()) &&
- android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- PlaybackService.this.setupNotification(info);
- } else if (!UserPreferences.isPersistNotify()) {
- PlaybackService.this.stopForeground(true);
- }
- }
- }
-
- @Override
public MediaSessionCompat getMediaSession() {
return PlaybackService.this.mediaSession;
}
@@ -1899,4 +1898,116 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PlaybackService.this.unregisterReceiver(receiver);
}
};
+
+ private boolean isStarted() {
+ return serviceManager.serviceInStartedState;
+ }
+
+ /**
+ * The helper that manages PlaybackService's foreground service life cycle and the associated
+ * notification control.
+ *
+ * The logic is adapted from a sample app from The Android Open Source Project.
+ * See https://github.com/googlesamples/android-MediaBrowserService/blob/6cf01be9ef82ca2dd653f03e2a4af0b075cc0021/Application/src/main/java/com/example/android/mediasession/service/MusicService.java#L211
+ *
+ */
+ private class ServiceManager {
+ private boolean serviceInStartedState;
+ private boolean toTreatNextPauseAsStopOnUI = false;
+
+ /**
+ *
+ * Entry point method for callers. Upon PlaybackState changes,
+ * the manager start/stop the PlaybackService as well as relevant notification
+ */
+ void onPlaybackStateChange(PlaybackStateCompat state) {
+ // Report the state to the MediaSession.
+
+ Log.v(TAG, "onPlaybackStateChange(" + (state != null ? state.getState() : "null") + ")");
+ try {
+ // Manage the started state of this service.
+ switch (state.getState()) {
+ case PlaybackStateCompat.STATE_CONNECTING:
+ // move the service to started, aka, making it foreground
+ // upon STATE_CONNECTING, i.e., in preparing to play a media.
+ // This is done so that in case the preparation takes a long time, e.g.,
+ // streaming over a slow network,
+ // the service won't be killed by the system prematurely.
+ moveServiceToStartedState(state);
+ break;
+ case PlaybackStateCompat.STATE_PLAYING:
+ moveServiceToStartedState(state);
+ break;
+ case PlaybackStateCompat.STATE_PAUSED:
+ updateNotificationForPause(state);
+ break;
+ case PlaybackStateCompat.STATE_STOPPED:
+ moveServiceOutOfStartedState(state);
+ break;
+ case PlaybackStateCompat.STATE_ERROR:
+ moveServiceOutOfStartedState(state);
+ break;
+ }
+ } finally {
+ if (toTreatNextPauseAsStopOnUI) {
+ Log.v(TAG, "onPlaybackStateChange() - toTreatNextPauseAsStopOnUI enabled. The actual state (should be PAUSED, aka 2): " + state.getState());
+ toTreatNextPauseAsStopOnUI = false;
+ }
+ }
+ }
+
+ /**
+ * Tell service manager that on the next state change, if the state is STATE_PAUSED,
+ * give UI treatment as if it is stopped.
+ *
+ * @see #handleKeycode(int, boolean) the use case
+ */
+ public void treatNextPauseAsStopOnUI() {
+ this.toTreatNextPauseAsStopOnUI = true;
+ }
+
+ public void showUIForStopState() {
+ Log.v(TAG, "serviceManager.showUIForStopState()");
+ stopForeground(true); // gets rid of persistent notification, to give the UI illusion of STOP
+ }
+
+ public void stopService() {
+ stopForeground(true);
+ stopSelf();
+ serviceInStartedState = false;
+ }
+
+ private void moveServiceToStartedState(PlaybackStateCompat state) {
+ if (!serviceInStartedState) {
+ ContextCompat.startForegroundService(
+ PlaybackService.this,
+ new Intent(PlaybackService.this, PlaybackService.class));
+ serviceInStartedState = true;
+ }
+
+ doSetupNotification();
+ }
+
+ private void updateNotificationForPause(PlaybackStateCompat state) {
+ doSetupNotification();
+ }
+
+ private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
+ stopService();
+ }
+
+ private void doSetupNotification() {
+ if (mediaPlayer != null && mediaPlayer.getPlayable() != null) {
+ // it updates notification and set foreground status
+ // based on player status (similar to PlaybackState)
+ setupNotification(mediaPlayer.getPlayable(), toTreatNextPauseAsStopOnUI);
+ } else {
+ // should not happen unless there are bugs.
+ Log.e(TAG, "doSetupNotification() - unexpectedly there is no playable. No notification setup done. mediaPlayer." + mediaPlayer);
+ }
+ }
+ }
+
+ private final ServiceManager serviceManager = new ServiceManager();
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index a2481b801..b8198fa63 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -148,10 +148,12 @@ public abstract class PlaybackServiceMediaPlayer {
public abstract boolean canSetSpeed();
/**
- * Sets the playback speed.
+ * Sets the playback parameters.
+ * - Speed
+ * - SkipSilence (ExoPlayer only)
* This method is executed on an internal executor service.
*/
- public abstract void setSpeed(float speed);
+ public abstract void setPlaybackParams(final float speed, final boolean skipSilence);
/**
* Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
index 8a222d7ec..4f2ae34f8 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java
@@ -12,7 +12,7 @@ public enum PlayerStatus {
INITIALIZING(9), // playback service is loading the Playable's metadata
INITIALIZED(10); // playback service was started, data source of media player was set.
- private int statusValue;
+ private final int statusValue;
private static final PlayerStatus[] fromOrdinalLookup;
static {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index e94874453..c63ac4416 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import android.util.Log;
import java.util.ArrayList;
@@ -20,11 +21,12 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm {
private static final String TAG = "APCleanupAlgorithm";
- /** the number of days after playback to wait before an item is eligible to be cleaned up */
- private final int numberOfDaysAfterPlayback;
+ /** the number of days after playback to wait before an item is eligible to be cleaned up.
+ Fractional for number of hours, e.g., 0.5 = 12 hours, 0.0416 = 1 hour. */
+ private final int numberOfHoursAfterPlayback;
- public APCleanupAlgorithm(int numberOfDaysAfterPlayback) {
- this.numberOfDaysAfterPlayback = numberOfDaysAfterPlayback;
+ public APCleanupAlgorithm(int numberOfHoursAfterPlayback) {
+ this.numberOfHoursAfterPlayback = numberOfHoursAfterPlayback;
}
/**
@@ -77,13 +79,17 @@ public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm {
return counter;
}
+ @VisibleForTesting
+ Date calcMostRecentDateForDeletion(@NonNull Date currentDate) {
+ return minusHours(currentDate, numberOfHoursAfterPlayback);
+ }
+
@NonNull
private List<FeedItem> getCandidates() {
List<FeedItem> candidates = new ArrayList<>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.DAY_OF_MONTH, -1 * numberOfDaysAfterPlayback);
- Date mostRecentDateForDeletion = cal.getTime();
+
+ Date mostRecentDateForDeletion = calcMostRecentDateForDeletion(new Date());
for (FeedItem item : downloadedItems) {
if (item.hasMedia()
&& item.getMedia().isDownloaded()
@@ -108,5 +114,16 @@ public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm {
return getNumEpisodesToCleanup(0);
}
- public int getNumberOfDaysAfterPlayback() { return numberOfDaysAfterPlayback; }
+ @VisibleForTesting
+ public int getNumberOfHoursAfterPlayback() { return numberOfHoursAfterPlayback; }
+
+ private static Date minusHours(Date baseDate, int numberOfHours) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(baseDate);
+
+ cal.add(Calendar.HOUR_OF_DAY, -1 * numberOfHours);
+
+ return cal.getTime();
+ }
+
}
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..1b579f99a 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
@@ -1,7 +1,10 @@
package de.danoeh.antennapod.core.storage;
import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -13,7 +16,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;
@@ -57,6 +59,7 @@ public final class DBReader {
* of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
* can be loaded separately with {@link #getFeedItemList(Feed)}.
*/
+ @NonNull
public static List<Feed> getFeedList() {
Log.d(TAG, "Extracting Feedlist");
@@ -69,13 +72,14 @@ public final class DBReader {
}
}
+ @NonNull
private static List<Feed> getFeedList(PodDBAdapter adapter) {
Cursor cursor = null;
try {
cursor = adapter.getAllFeedsCursor();
List<Feed> feeds = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
- Feed feed = extractFeedFromCursorRow(adapter, cursor);
+ Feed feed = extractFeedFromCursorRow(cursor);
feeds.add(feed);
}
return feeds;
@@ -198,28 +202,19 @@ public final class DBReader {
}
}
+ @NonNull
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) {
@@ -253,28 +248,14 @@ public final class DBReader {
return result;
}
- 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;
- }
-
+ private static Feed extractFeedFromCursorRow(Cursor cursor) {
Feed feed = Feed.fromCursor(cursor);
- if (image != null) {
- feed.setImage(image);
- image.setOwner(feed);
- }
-
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
-
return feed;
}
+ @NonNull
static List<FeedItem> getQueue(PodDBAdapter adapter) {
Log.d(TAG, "getQueue()");
Cursor cursor = null;
@@ -331,6 +312,7 @@ public final class DBReader {
* @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
+ @NonNull
public static List<FeedItem> getQueue() {
Log.d(TAG, "getQueue() called");
@@ -348,6 +330,7 @@ public final class DBReader {
*
* @return A list of FeedItems whose episdoe has been downloaded.
*/
+ @NonNull
public static List<FeedItem> getDownloadedItems() {
Log.d(TAG, "getDownloadedItems() called");
@@ -438,6 +421,7 @@ public final class DBReader {
*
* @param limit The maximum number of episodes that should be loaded.
*/
+ @NonNull
public static List<FeedItem> getRecentlyPublishedEpisodes(int limit) {
Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "limit = [" + limit + "]");
@@ -464,6 +448,7 @@ public final class DBReader {
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
*/
+ @NonNull
public static List<FeedItem> getPlaybackHistory() {
Log.d(TAG, "getPlaybackHistory() called");
@@ -599,13 +584,14 @@ public final class DBReader {
}
}
+ @Nullable
static Feed getFeed(final long feedId, PodDBAdapter adapter) {
Feed feed = null;
Cursor cursor = null;
try {
cursor = adapter.getFeedCursor(feedId);
if (cursor.moveToNext()) {
- feed = extractFeedFromCursorRow(adapter, cursor);
+ feed = extractFeedFromCursorRow(cursor);
feed.setItems(getFeedItemList(feed));
} else {
Log.e(TAG, "getFeed could not find feed with id " + feedId);
@@ -618,6 +604,7 @@ public final class DBReader {
}
}
+ @Nullable
private static FeedItem getFeedItem(final long itemId, PodDBAdapter adapter) {
Log.d(TAG, "Loading feeditem with id " + itemId);
@@ -651,6 +638,7 @@ public final class DBReader {
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
+ @Nullable
public static FeedItem getFeedItem(final long itemId) {
Log.d(TAG, "getFeedItem() called with: " + "itemId = [" + itemId + "]");
@@ -663,6 +651,7 @@ public final class DBReader {
}
}
+ @Nullable
private static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
Cursor cursor = null;
@@ -714,7 +703,7 @@ public final class DBReader {
if (cursor.moveToFirst()) {
String username = cursor.getString(0);
String password = cursor.getString(1);
- if (username != null && password != null) {
+ if (!TextUtils.isEmpty(username) && password != null) {
credentials = username + ":" + password;
} else {
credentials = "";
@@ -839,62 +828,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
@@ -938,6 +871,7 @@ public final class DBReader {
* countAll calculation time
* @return The StatisticsInfo object
*/
+ @NonNull
public static StatisticsData getStatistics(boolean sortByCountAll) {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -961,10 +895,6 @@ public final class DBReader {
continue;
}
- // played duration used to be reset when the item is added to the playback history
- if (media.getPlaybackCompletionDate() != null) {
- feedPlayedTime += media.getDuration() / 1000;
- }
feedPlayedTime += media.getPlayedDuration() / 1000;
if (item.isPlayed()) {
@@ -1088,7 +1018,7 @@ public final class DBReader {
Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor();
if (feedCursor.moveToFirst()) {
do {
- result.add(extractFeedFromCursorRow(adapter, feedCursor));
+ result.add(extractFeedFromCursorRow(feedCursor));
} while (feedCursor.moveToNext());
}
feedCursor.close();
@@ -1108,6 +1038,7 @@ public final class DBReader {
* the list of subscriptions, the number of items in the queue and the number of unread
* items.
*/
+ @NonNull
public static NavDrawerData getNavDrawerData() {
Log.d(TAG, "getNavDrawerData() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
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 da500fd3e..dab8e19b5 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
@@ -1,11 +1,9 @@
package de.danoeh.antennapod.core.storage;
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;
@@ -19,7 +17,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.core.ClientConfig;
@@ -30,12 +27,11 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
-import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
@@ -142,8 +138,7 @@ public final class DBTasks {
} catch (MediaFileNotFoundException e) {
e.printStackTrace();
if (media.isPlaying()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
}
notifyMissingFeedMediaFile(context, media);
}
@@ -198,8 +193,10 @@ public final class DBTasks {
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetSyncService.sendSyncIntent(context);
}
- Log.d(TAG, "refreshAllFeeds autodownload");
- autodownloadUndownloadedItems(context);
+ // Note: automatic download of episodes will be done but not here.
+ // Instead it is done after all feeds have been refreshed (asynchronously),
+ // in DownloadService.onDestroy()
+ // See Issue #2577 for the details of the rationale
if (callback != null) {
callback.run();
@@ -207,6 +204,11 @@ public final class DBTasks {
}).start();
}
+ public static long getLastRefreshAllFeedsTimeMillis(final Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(DBTasks.PREF_NAME, MODE_PRIVATE);
+ return prefs.getLong(DBTasks.PREF_LAST_REFRESH, 0);
+ }
+
/**
* @param context
* @param feedList the list of feeds to refresh
@@ -237,27 +239,6 @@ public final class DBTasks {
}
/**
- * Downloads all pages of the given feed.
- *
- * @param context Used for requesting the download.
- * @param feed The Feed object.
- */
- public static void refreshCompleteFeed(final Context context, final Feed feed) {
- try {
- refreshFeed(context, feed, true, false);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DBWriter.addDownloadStatus(
- new DownloadStatus(feed, feed
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR, false, e
- .getMessage()
- )
- );
- }
- }
-
- /**
* Downloads all pages of the given feed even if feed has not been modified since last refresh
*
* @param context Used for requesting the download.
@@ -338,31 +319,6 @@ public final class DBTasks {
DownloadRequester.getInstance().downloadFeed(context, f, loadAllPages, force);
}
- /*
- * Checks if the app should refresh all feeds, i.e. if the last auto refresh failed.
- *
- * The feeds are only refreshed if an update interval or time of day is set and the last
- * (successful) refresh was before the last interval or more than a day ago, respectively.
- */
- public static void checkShouldRefreshFeeds(Context context) {
- long interval = 0;
- if(UserPreferences.getUpdateInterval() > 0) {
- interval = UserPreferences.getUpdateInterval();
- } else if(UserPreferences.getUpdateTimeOfDay().length > 0){
- interval = TimeUnit.DAYS.toMillis(1);
- }
- if(interval == 0) { // auto refresh is disabled
- return;
- }
- SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
- long lastRefresh = prefs.getLong(PREF_LAST_REFRESH, 0);
- Log.d(TAG, "last refresh: " + Converter.getDurationStringLocalized(context,
- System.currentTimeMillis() - lastRefresh) + " ago");
- if(lastRefresh <= System.currentTimeMillis() - interval) {
- DBTasks.refreshAllFeeds(context, null);
- }
- }
-
/**
* Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the
* DB and send a FeedUpdateBroadcast.
@@ -378,27 +334,6 @@ public final class DBTasks {
}
/**
- * Request the download of all objects in the queue. from a separate Thread.
- *
- * @param context Used for requesting the download an accessing the database.
- */
- public static void downloadAllItemsInQueue(final Context context) {
- new Thread() {
- public void run() {
- List<FeedItem> queue = DBReader.getQueue();
- if (!queue.isEmpty()) {
- try {
- downloadFeedItems(context,
- queue.toArray(new FeedItem[queue.size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- }
-
- /**
* Requests the download of a list of FeedItem objects.
*
* @param context Used for requesting the download and accessing the DB.
@@ -817,10 +752,8 @@ public final class DBTasks {
*/
abstract static class QueryTask<T> implements Callable<T> {
private T result;
- private final Context context;
public QueryTask(Context context) {
- this.context = context;
}
@Override
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..0beb765e7
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
@@ -0,0 +1,293 @@
+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 8cdf82e15..f7226729a 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
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
import android.util.Log;
import org.shredzone.flattr4j.model.Flattr;
@@ -32,7 +33,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;
@@ -42,6 +42,7 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.Permutor;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
@@ -80,65 +81,72 @@ public class DBWriter {
* @param context A context that is used for opening a database connection.
* @param mediaId ID of the FeedMedia object whose downloaded file should be deleted.
*/
- public static Future<?> deleteFeedMediaOfItem(final Context context,
+ public static Future<?> deleteFeedMediaOfItem(@NonNull final Context context,
final long mediaId) {
return dbExec.submit(() -> {
final FeedMedia media = DBReader.getFeedMedia(mediaId);
if (media != null) {
- Log.i(TAG, String.format("Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s",
- media.getId(), media.getEpisodeTitle(), String.valueOf(media.isDownloaded())));
- if (media.isDownloaded()) {
- // delete downloaded media file
- File mediaFile = new File(media.getFile_url());
- if (mediaFile.exists() && !mediaFile.delete()) {
- MessageEvent evt = new MessageEvent(context.getString(R.string.delete_failed));
- EventBus.getDefault().post(evt);
- return;
- }
- media.setDownloaded(false);
- media.setFile_url(null);
- media.setHasEmbeddedPicture(false);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setMedia(media);
- adapter.close();
-
- // If media is currently being played, change playback
- // type to 'stream' and shutdown playback service
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
- if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
- if (media.getId() == PlaybackPreferences
- .getCurrentlyPlayingFeedMediaId()) {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(
- PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
- true);
- editor.commit();
- }
- if (PlaybackPreferences
- .getCurrentlyPlayingFeedMediaId() == media
- .getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- }
- // Gpodder: queue delete action for synchronization
- if(GpodnetPreferences.loggedIn()) {
- FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
- .currentDeviceId()
- .currentTimestamp()
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
- }
+ boolean result = deleteFeedMediaSynchronous(context, media);
+
+ if (result && UserPreferences.shouldDeleteRemoveFromQueue()) {
+ DBWriter.removeQueueItemSynchronous(context, false, media.getItem().getId());
}
- EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
- EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}
+ private static boolean deleteFeedMediaSynchronous(@NonNull Context context, @NonNull FeedMedia media) {
+ Log.i(TAG, String.format("Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s",
+ media.getId(), media.getEpisodeTitle(), String.valueOf(media.isDownloaded())));
+ if (media.isDownloaded()) {
+ // delete downloaded media file
+ File mediaFile = new File(media.getFile_url());
+ if (mediaFile.exists() && !mediaFile.delete()) {
+ MessageEvent evt = new MessageEvent(context.getString(R.string.delete_failed));
+ EventBus.getDefault().post(evt);
+ return false;
+ }
+ media.setDownloaded(false);
+ media.setFile_url(null);
+ media.setHasEmbeddedPicture(false);
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+
+ // If media is currently being played, change playback
+ // type to 'stream' and shutdown playback service
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+ if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
+ if (media.getId() == PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId()) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(
+ PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
+ true);
+ editor.commit();
+ }
+ if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == media.getId()) {
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
+ }
+ }
+ // Gpodder: queue delete action for synchronization
+ if(GpodnetPreferences.loggedIn()) {
+ FeedItem item = media.getItem();
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.DELETE)
+ .currentDeviceId()
+ .currentTimestamp()
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
+ }
+ EventBus.getDefault().post(FeedItemEvent.deletedMedia(Collections.singletonList(media.getItem())));
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+
+ return true;
+ }
+
/**
* Deletes a Feed and all downloaded files of its components like images and downloaded episodes.
*
@@ -157,8 +165,7 @@ public class DBWriter {
if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
&& PlaybackPreferences.getLastPlayedFeedId() == feed
.getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
SharedPreferences.Editor editor = prefs.edit();
editor.putLong(
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
@@ -166,17 +173,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<>();
@@ -200,16 +196,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();
@@ -437,38 +423,68 @@ public class DBWriter {
/**
* Removes a FeedItem object from the queue.
- *
- * @param context A context that is used for opening a database connection.
- * @param item FeedItem that should be removed.
+ * @param context A context that is used for opening a database connection.
* @param performAutoDownload true if an auto-download process should be started after the operation.
+ * @param item FeedItem that should be removed.
*/
public static Future<?> removeQueueItem(final Context context,
- final FeedItem item, final boolean performAutoDownload) {
- return dbExec.submit(() -> {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
+ final boolean performAutoDownload, final FeedItem item) {
+ return dbExec.submit(() -> removeQueueItemSynchronous(context, performAutoDownload, item.getId()));
+ }
- if (queue != null) {
- int position = queue.indexOf(item);
+ public static Future<?> removeQueueItem(final Context context, final boolean performAutoDownload,
+ final long... itemIds) {
+ return dbExec.submit(() -> removeQueueItemSynchronous(context, performAutoDownload, itemIds));
+ }
+
+ private static void removeQueueItemSynchronous(final Context context,
+ final boolean performAutoDownload,
+ final long... itemIds) {
+ if (itemIds.length < 1) {
+ return;
+ }
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+
+ if (queue != null) {
+ boolean queueModified = false;
+ List<QueueEvent> events = new ArrayList<>();
+ List<FeedItem> updatedItems = new ArrayList<>();
+ for (long itemId : itemIds) {
+ int position = indexInItemList(queue, itemId);
if (position >= 0) {
+ final FeedItem item = DBReader.getFeedItem(itemId);
+ if (item == null) {
+ Log.e(TAG, "removeQueueItem - item in queue but somehow cannot be loaded." +
+ " Item ignored. It should never happen. id:" + itemId);
+ continue;
+ }
queue.remove(position);
- adapter.setQueue(queue);
item.removeTag(FeedItem.TAG_QUEUE);
- EventBus.getDefault().post(QueueEvent.removed(item));
- EventBus.getDefault().post(FeedItemEvent.updated(item));
+ events.add(QueueEvent.removed(item));
+ updatedItems.add(item);
+ queueModified = true;
} else {
- Log.w(TAG, "Queue was not modified by call to removeQueueItem");
+ Log.v(TAG, "removeQueueItem - item not in queue:" + itemId);
}
- } else {
- Log.e(TAG, "removeQueueItem: Could not load queue");
}
- adapter.close();
- if (performAutoDownload) {
- DBTasks.autodownloadUndownloadedItems(context);
+ if (queueModified) {
+ adapter.setQueue(queue);
+ for (QueueEvent event : events) {
+ EventBus.getDefault().post(event);
+ }
+ EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
+ } else {
+ Log.w(TAG, "Queue was not modified by call to removeQueueItem");
}
- });
-
+ } else {
+ Log.e(TAG, "removeQueueItem: Could not load queue");
+ }
+ adapter.close();
+ if (performAutoDownload) {
+ DBTasks.autodownloadUndownloadedItems(context);
+ }
}
public static Future<?> addFavoriteItem(final FeedItem item) {
@@ -482,22 +498,6 @@ public class DBWriter {
});
}
- public static Future<?> addFavoriteItemById(final long itemId) {
- return dbExec.submit(() -> {
- final FeedItem item = DBReader.getFeedItem(itemId);
- if (item == null) {
- Log.d(TAG, "Can't find item for itemId " + itemId);
- return;
- }
- final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
- adapter.addFavoriteItem(item);
- adapter.close();
- item.addTag(FeedItem.TAG_FAVORITE);
- EventBus.getDefault().post(FavoritesEvent.added(item));
- EventBus.getDefault().post(FeedItemEvent.updated(item));
- });
- }
-
public static Future<?> removeFavoriteItem(final FeedItem item) {
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
@@ -633,11 +633,13 @@ public class DBWriter {
* FeedItem.NEW, FeedItem.UNPLAYED
* @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
*/
+ @NonNull
public static Future<?> markItemPlayed(FeedItem item, int played, boolean resetMediaPosition) {
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
return markItemPlayed(item.getId(), played, mediaId, resetMediaPosition);
}
+ @NonNull
private static Future<?> markItemPlayed(final long itemId,
final int played,
final long mediaId,
@@ -786,21 +788,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) {
@@ -829,12 +816,17 @@ public class DBWriter {
}
private static boolean itemListContains(List<FeedItem> items, long itemId) {
- for (FeedItem item : items) {
+ return indexInItemList(items, itemId) >= 0;
+ }
+
+ private static int indexInItemList(List<FeedItem> items, long itemId) {
+ for (int i = 0; i < items.size(); i ++) {
+ FeedItem item = items.get(i);
if (item.getId() == itemId) {
- return true;
+ return i;
}
}
- return false;
+ return -1;
}
/**
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 7d4b737db..892a4675a 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
@@ -23,6 +23,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.util.FileNameGenerator;
+import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.URLChecker;
@@ -242,6 +243,7 @@ public class DownloadRequester {
Log.d(TAG, "Cancelling download with url " + downloadUrl);
Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl);
+ cancelIntent.setPackage(context.getPackageName());
context.sendBroadcast(cancelIntent);
}
@@ -250,8 +252,7 @@ public class DownloadRequester {
*/
public synchronized void cancelAllDownloads(Context context) {
Log.d(TAG, "Cancelling all running downloads");
- context.sendBroadcast(new Intent(
- DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
+ IntentUtils.sendLocalBroadcast(context, DownloadService.ACTION_CANCEL_ALL_DOWNLOADS);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
index aa5706ad0..4093c41a5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
+import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collections;
@@ -19,6 +20,8 @@ import de.danoeh.antennapod.core.util.comparator.SearchResultValueComparator;
* Performs search on Feeds and FeedItems
*/
public class FeedSearcher {
+ private FeedSearcher(){}
+
private static final String TAG = "FeedSearcher";
@@ -33,6 +36,7 @@ public class FeedSearcher {
* @param selectedFeed feed to search, 0 to search through all feeds
* @return list of episodes containing the query
*/
+ @NonNull
public static List<SearchResult> performSearch(final Context context,
final String query, final long selectedFeed) {
final int values[] = {2, 1, 0, 0, 0, 0};
@@ -43,7 +47,7 @@ public class FeedSearcher {
context.getString(R.string.found_in_authors_label),
context.getString(R.string.found_in_feeds_label)};
- List<SearchResult> result = new ArrayList<>();
+ final List<SearchResult> result = new ArrayList<>();
List<FutureTask<List<FeedItem>>> tasks = new ArrayList<>();
tasks.add(DBTasks.searchFeedItemTitle(context, selectedFeed, query));
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..1daa43025 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
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.storage;
+import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -12,9 +13,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+import org.apache.commons.io.FileUtils;
+
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -26,8 +32,6 @@ 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;
@@ -36,7 +40,6 @@ import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.greenrobot.event.EventBus;
-import org.apache.commons.io.FileUtils;
// TODO Remove media column from feeditem table
@@ -74,8 +77,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 +118,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 +137,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 +159,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 +190,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 +234,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 +267,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 +277,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,
@@ -310,7 +303,6 @@ public class PodDBAdapter {
private static Context context;
private static volatile SQLiteDatabase db;
- private static int counter = 0;
public static void init(Context context) {
PodDBAdapter.context = context.getApplicationContext();
@@ -330,20 +322,20 @@ public class PodDBAdapter {
}
public synchronized PodDBAdapter open() {
- counter++;
- Log.v(TAG, "Opening DB #" + counter);
-
if (db == null || !db.isOpen() || db.isReadOnly()) {
db = openDb();
}
return this;
}
+ @SuppressLint("NewApi")
private SQLiteDatabase openDb() {
SQLiteDatabase newDb;
try {
newDb = SingletonHolder.dbHelper.getWritableDatabase();
- newDb.enableWriteAheadLogging();
+ if (Build.VERSION.SDK_INT >= 16) {
+ newDb.disableWriteAheadLogging();
+ }
} catch (SQLException ex) {
Log.e(TAG, Log.getStackTraceString(ex));
newDb = SingletonHolder.dbHelper.getReadableDatabase();
@@ -352,14 +344,7 @@ public class PodDBAdapter {
}
public synchronized void close() {
- counter--;
- Log.v(TAG, "Closing DB #" + counter);
-
- if (counter == 0) {
- Log.v(TAG, "Closing DB, really");
- db.close();
- db = null;
- }
+ // do nothing
}
public static boolean deleteDatabase() {
@@ -388,12 +373,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 +430,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
*/
@@ -608,31 +541,6 @@ public class PodDBAdapter {
}
/**
- * Counts feeds and feed items in the flattr queue
- */
- public int getFlattrQueueSize() {
- int res = 0;
- Cursor c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s",
- TABLE_NAME_FEEDS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null);
- if (c.moveToFirst()) {
- res = c.getInt(0);
- c.close();
- } else {
- Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feeds");
- }
- c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s",
- TABLE_NAME_FEED_ITEMS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null);
- if (c.moveToFirst()) {
- res += c.getInt(0);
- c.close();
- } else {
- Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feed items");
- }
-
- return res;
- }
-
- /**
* Updates the download URL of a Feed.
*/
public void setFeedDownloadUrl(String original, String updated) {
@@ -759,12 +667,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));
@@ -944,17 +847,6 @@ public class PodDBAdapter {
return count > 0;
}
- public long getDownloadLogSize() {
- final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG);
- Cursor result = db.rawQuery(query, null);
- long count = 0;
- if (result.moveToFirst()) {
- count = result.getLong(0);
- }
- result.close();
- return count;
- }
-
public void setQueue(List<FeedItem> queue) {
ContentValues values = new ContentValues();
try {
@@ -993,11 +885,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 +895,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 +905,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);
@@ -1094,18 +975,6 @@ public class PodDBAdapter {
}
/**
- * Returns a cursor for a DB query in the FeedMedia table for a given ID.
- *
- * @param item The item you want to get the FeedMedia from
- * @return The cursor of the query
- */
- public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
- return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
- new String[]{String.valueOf(item.getMedia().getId())}, null,
- null, null);
- }
-
- /**
* Returns a cursor for a DB query in the FeedImages table for given IDs.
*
* @param imageIds IDs of the images
@@ -1363,17 +1232,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);
}
@@ -1389,9 +1253,15 @@ public class PodDBAdapter {
}
public final int getNumberOfNewItems() {
- final String query = "SELECT COUNT(" + KEY_ID + ")"
- + " FROM " + TABLE_NAME_FEED_ITEMS
- + " WHERE " + KEY_READ + "=" + FeedItem.NEW;
+ Object[] args = new String[]{
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS,
+ TABLE_NAME_FEEDS,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.NEW
+ + " AND " + TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED + " > 0"
+ };
+ final String query = String.format("SELECT COUNT(%s) FROM %s INNER JOIN %s ON %s WHERE %s", args);
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
@@ -1672,7 +1542,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 +1563,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 +1570,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 +1584,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/handler/TypeGetter.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
index ee0a71f30..b4c77e58d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/TypeGetter.java
@@ -54,18 +54,19 @@ public class TypeGetter {
return Type.ATOM;
case RSS_ROOT:
String strVersion = xpp.getAttributeValue(null, "version");
- if (strVersion != null) {
- if (strVersion.equals("2.0")) {
- feed.setType(Feed.TYPE_RSS2);
- Log.d(TAG, "Recognized type RSS 2.0");
- return Type.RSS20;
- } else if (strVersion.equals("0.91")
- || strVersion.equals("0.92")) {
- Log.d(TAG, "Recognized type RSS 0.91/0.92");
- return Type.RSS091;
- }
+ if (strVersion == null) {
+ feed.setType(Feed.TYPE_RSS2);
+ Log.d(TAG, "Assuming type RSS 2.0");
+ return Type.RSS20;
+ } else if (strVersion.equals("2.0")) {
+ feed.setType(Feed.TYPE_RSS2);
+ Log.d(TAG, "Recognized type RSS 2.0");
+ return Type.RSS20;
+ } else if (strVersion.equals("0.91") || strVersion.equals("0.92")) {
+ Log.d(TAG, "Recognized type RSS 0.91/0.92");
+ return Type.RSS091;
}
- throw new UnsupportedFeedtypeException(Type.INVALID);
+ throw new UnsupportedFeedtypeException("Unsupported rss version");
default:
Log.d(TAG, "Type is invalid");
throw new UnsupportedFeedtypeException(Type.INVALID, tag);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java
index 606f93faf..fd7d0a4e1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/UnsupportedFeedtypeException.java
@@ -5,7 +5,8 @@ import de.danoeh.antennapod.core.syndication.handler.TypeGetter.Type;
public class UnsupportedFeedtypeException extends Exception {
private static final long serialVersionUID = 9105878964928170669L;
private final TypeGetter.Type type;
- private String rootElement;
+ private String rootElement;
+ private String message = null;
public UnsupportedFeedtypeException(Type type) {
super();
@@ -17,6 +18,11 @@ public class UnsupportedFeedtypeException extends Exception {
this.rootElement = rootElement;
}
+ public UnsupportedFeedtypeException(String message) {
+ this.message = message;
+ type = Type.INVALID;
+ }
+
public TypeGetter.Type getType() {
return type;
}
@@ -27,7 +33,9 @@ public class UnsupportedFeedtypeException extends Exception {
@Override
public String getMessage() {
- if (type == TypeGetter.Type.INVALID) {
+ if (message != null) {
+ return message;
+ } else if (type == TypeGetter.Type.INVALID) {
return "Invalid type";
} else {
return "Type " + type + " not supported";
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..b3b8a40ce 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);
}
}
}
@@ -55,6 +47,9 @@ public class NSITunes extends Namespace {
if(state.getContentBuf() == null) {
return;
}
+ SyndElement secondElement = state.getSecondTag();
+ String second = secondElement.getName();
+
if (AUTHOR.equals(localName)) {
if (state.getFeed() != null) {
String author = state.getContentBuf().toString();
@@ -103,10 +98,9 @@ public class NSITunes extends Namespace {
}
if (state.getCurrentItem() != null &&
(TextUtils.isEmpty(state.getCurrentItem().getDescription()) ||
- state.getCurrentItem().getDescription().length() * 1.25 < summary.length())
- ) {
+ state.getCurrentItem().getDescription().length() * 1.25 < summary.length())) {
state.getCurrentItem().setDescription(summary);
- } else if (state.getFeed() != null) {
+ } else if (NSRSS20.CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setDescription(summary);
}
}
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..e391af1b2 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
@@ -5,8 +5,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;
@@ -23,9 +21,6 @@ public class NSRSS20 extends Namespace {
private static final String TAG = "NSRSS20";
- private static final String NSTAG = "rss";
- private static final String NSURI = "";
-
public static final String CHANNEL = "channel";
public static final String ITEM = "item";
private static final String GUID = "guid";
@@ -77,17 +72,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 +118,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 +129,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/Namespace.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java
index 1836bbec1..e5fbdb9bb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java
@@ -15,7 +15,6 @@ public abstract class Namespace {
public abstract SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes);
/** Called by a Feedhandler when in endElement and it detects a namespace element
- * @return true if namespace handled the element, false if it ignored it
* */
public abstract void handleElementEnd(String localName, HandlerState state);
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..83957456a 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;
@@ -48,8 +47,6 @@ public class NSAtom extends Namespace {
private static final String LINK_REL_ARCHIVES = "archives";
private static final String LINK_REL_ENCLOSURE = "enclosure";
private static final String LINK_REL_PAYMENT = "payment";
- private static final String LINK_REL_RELATED = "related";
- private static final String LINK_REL_SELF = "self";
private static final String LINK_REL_NEXT = "next";
// type-values
private static final String LINK_TYPE_ATOM = "application/atom+xml";
@@ -210,10 +207,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/Consumer.java b/core/src/main/java/de/danoeh/antennapod/core/util/Consumer.java
new file mode 100644
index 000000000..13a87af0d
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Consumer.java
@@ -0,0 +1,5 @@
+package de.danoeh.antennapod.core.util;
+
+public interface Consumer<T> {
+ void accept(T t);
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
index b513fbe99..6ecca941a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
@@ -28,7 +28,6 @@ public final class Converter {
/** Determines the length of the number for best readability.*/
private static final int NUM_LENGTH = 1024;
- private static final int DAYS_MIL = 86400000;
private static final int HOURS_MIL = 3600000;
private static final int MINUTES_MIL = 60000;
private static final int SECONDS_MIL = 1000;
@@ -75,13 +74,14 @@ public final class Converter {
return String.format(Locale.getDefault(), "%02d:%02d:%02d", h, m, s);
}
- /** Converts milliseconds to a string containing hours and minutes */
- public static String getDurationStringShort(int duration) {
- int h = duration / HOURS_MIL;
- int rest = duration - h * HOURS_MIL;
- int m = rest / MINUTES_MIL;
-
- return String.format(Locale.getDefault(), "%02d:%02d", h, m);
+ /** Converts milliseconds to a string containing hours and minutes or minutes and seconds*/
+ public static String getDurationStringShort(int duration, boolean durationIsInHours) {
+ int firstPartBase = durationIsInHours ? HOURS_MIL : MINUTES_MIL;
+ int firstPart = duration / firstPartBase;
+ int leftoverFromFirstPart = duration - firstPart * firstPartBase;
+ int secondPart = leftoverFromFirstPart / (durationIsInHours ? MINUTES_MIL : SECONDS_MIL);
+
+ return String.format(Locale.getDefault(), "%02d:%02d", firstPart, secondPart);
}
/** Converts long duration string (HH:MM:SS) to milliseconds. */
@@ -95,14 +95,20 @@ public final class Converter {
Integer.parseInt(parts[2]) * 1000;
}
- /** Converts short duration string (HH:MM) to milliseconds. */
- public static int durationStringShortToMs(String input) {
+ /**
+ * Converts short duration string (XX:YY) to milliseconds. If durationIsInHours is true then the
+ * format is HH:MM, otherwise it's MM:SS.
+ * */
+ public static int durationStringShortToMs(String input, boolean durationIsInHours) {
String[] parts = input.split(":");
if (parts.length != 2) {
return 0;
}
- return Integer.parseInt(parts[0]) * 3600 * 1000 +
- Integer.parseInt(parts[1]) * 1000 * 60;
+
+ int modifier = durationIsInHours ? 60 : 1;
+
+ return Integer.parseInt(parts[0]) * 60 * 1000 * modifier+
+ Integer.parseInt(parts[1]) * 1000 * modifier;
}
/** Converts milliseconds to a localized string containing hours and minutes */
@@ -131,6 +137,7 @@ public final class Converter {
return String.format(Locale.getDefault(), "%.1f ", hours) + context.getString(R.string.time_hours);
}
+
/**
* Converts the volume as read as the progress from a SeekBar scaled to 100 and as saved in
* UserPreferences to the format taken by setVolume methods.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
index 5056ea274..5141e3a78 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
@@ -17,7 +17,9 @@ import java.util.TimeZone;
*/
public class DateUtils {
- private static final String TAG = "DateUtils";
+ private DateUtils(){}
+
+ private static final String TAG = "DateUtils";
private static final TimeZone defaultTimezone = TimeZone.getTimeZone("GMT");
@@ -85,7 +87,8 @@ public class DateUtils {
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
"yyyy-MM-ddZ",
- "yyyy-MM-dd"
+ "yyyy-MM-dd",
+ "EEE d MMM yyyy HH:mm:ss 'GMT'Z (z)"
};
SimpleDateFormat parser = new SimpleDateFormat("", Locale.US);
@@ -134,7 +137,7 @@ public class DateUtils {
if (parts.length >= 2) {
result += Integer.parseInt(parts[idx]) * 60000L;
idx++;
- result += (Float.parseFloat(parts[idx])) * 1000L;
+ result += (long) (Float.parseFloat(parts[idx]) * 1000L);
}
return result;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
deleted file mode 100644
index fb1b0dc8f..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package de.danoeh.antennapod.core.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.FeedItem;
-
-class EpisodeFilter {
-
- private EpisodeFilter() {
-
- }
-
- /** Return a copy of the itemlist without items which have no media. */
- public static ArrayList<FeedItem> getEpisodeList(List<FeedItem> items) {
- ArrayList<FeedItem> episodes = new ArrayList<>(items);
- for (FeedItem item : items) {
- if (item.getMedia() == null) {
- episodes.remove(item);
- }
- }
- return episodes;
- }
-
- public static int countItemsWithEpisodes(List<FeedItem> items) {
- int count = 0;
- for (FeedItem item : items) {
- if (item.getMedia() != null) {
- count++;
- }
- }
- return count;
- }
-
- public static FeedItem accessEpisodeByIndex(List<FeedItem> items,
- int position) {
- int count = 0;
- for (FeedItem item : items) {
-
- if (item.getMedia() != null) {
- if (count == position) {
- return item;
- } else {
- count++;
- }
- }
- }
- return null;
- }
-}
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 76a6549ae..826c06822 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
@@ -5,6 +5,7 @@ import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
public class FeedItemUtil {
+ private FeedItemUtil(){}
public static int indexOfItemWithDownloadUrl(List<FeedItem> items, String downloadUrl) {
if(items == 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
index 24e0da9ed..afaf13390 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
@@ -2,20 +2,30 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.util.Log;
+
+import org.awaitility.core.ConditionTimeoutException;
+
+import java.util.concurrent.TimeUnit;
+
import de.danoeh.antennapod.core.storage.DBTasks;
+import static org.awaitility.Awaitility.with;
+
public class FeedUpdateUtils {
private static final String TAG = "FeedUpdateUtils";
- private FeedUpdateUtils() {
-
- }
+ private FeedUpdateUtils() {}
public static void startAutoUpdate(Context context, Runnable callback) {
- if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
+ try {
+ with().pollInterval(1, TimeUnit.SECONDS)
+ .await()
+ .atMost(10, TimeUnit.SECONDS)
+ .until(() -> NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed());
DBTasks.refreshAllFeeds(context, null, callback);
- } else {
+ } catch (ConditionTimeoutException ignore) {
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/Function.java b/core/src/main/java/de/danoeh/antennapod/core/util/Function.java
new file mode 100644
index 000000000..c4f4ad68a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Function.java
@@ -0,0 +1,7 @@
+package de.danoeh.antennapod.core.util;
+
+import io.reactivex.annotations.NonNull;
+
+public interface Function<T, R> {
+ R apply(@NonNull T t);
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
index 1571b71c2..e81ab47ed 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java
@@ -8,6 +8,7 @@ import android.content.pm.ResolveInfo;
import java.util.List;
public class IntentUtils {
+ private IntentUtils(){}
/*
* Checks if there is at least one exported activity that can be performed for the intent
@@ -23,4 +24,8 @@ public class IntentUtils {
return false;
}
+ public static void sendLocalBroadcast(Context context, String action) {
+ context.sendBroadcast(new Intent(action).setPackage(context.getPackageName()));
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
index 7ec7f84c4..90e0b0981 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
@@ -5,6 +5,9 @@ import android.support.v4.util.ArrayMap;
import java.nio.charset.Charset;
public class LangUtils {
+
+ private LangUtils(){}
+
public static final Charset UTF_8 = Charset.forName("UTF-8");
private static final ArrayMap<String, String> languages;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
index cbda583fa..9bdd375ce 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
@@ -18,15 +18,16 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.storage.DBWriter;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import rx.Observable;
-import rx.Subscriber;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
public class NetworkUtils {
+ private NetworkUtils(){}
private static final String TAG = NetworkUtils.class.getSimpleName();
@@ -89,9 +90,13 @@ public class NetworkUtils {
return info != null && info.isConnected();
}
- public static boolean isDownloadAllowed() {
- return UserPreferences.isAllowMobileUpdate() || !NetworkUtils.isNetworkMetered();
- }
+ public static boolean isDownloadAllowed() {
+ return UserPreferences.isAllowMobileUpdate() || !NetworkUtils.isNetworkMetered();
+ }
+
+ public static boolean isImageAllowed() {
+ return UserPreferences.isAllowMobileImages() || !NetworkUtils.isNetworkMetered();
+ }
private static boolean isNetworkMetered() {
ConnectivityManager connManager = (ConnectivityManager) context
@@ -111,11 +116,10 @@ public class NetworkUtils {
return null;
}
- public static Observable<Long> getFeedMediaSizeObservable(FeedMedia media) {
- return Observable.create((Observable.OnSubscribe<Long>) subscriber -> {
+ public static Single<Long> getFeedMediaSizeObservable(FeedMedia media) {
+ return Single.create((SingleOnSubscribe<Long>) emitter -> {
if (!NetworkUtils.isDownloadAllowed()) {
- subscriber.onNext(0L);
- subscriber.onCompleted();
+ emitter.onSuccess(0L);
return;
}
long size = Integer.MIN_VALUE;
@@ -129,8 +133,7 @@ public class NetworkUtils {
String url = media.getDownload_url();
if(TextUtils.isEmpty(url)) {
- subscriber.onNext(0L);
- subscriber.onCompleted();
+ emitter.onSuccess(0L);
return;
}
@@ -150,8 +153,7 @@ public class NetworkUtils {
}
}
} catch (IOException e) {
- subscriber.onNext(0L);
- subscriber.onCompleted();
+ emitter.onSuccess(0L);
Log.e(TAG, Log.getStackTraceString(e));
return; // better luck next time
}
@@ -163,11 +165,10 @@ public class NetworkUtils {
} else {
media.setSize(size);
}
- subscriber.onNext(size);
- subscriber.onCompleted();
+ emitter.onSuccess(size);
DBWriter.setFeedMedia(media);
})
- .subscribeOn(Schedulers.newThread())
+ .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java
new file mode 100644
index 000000000..0fe11ec53
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Optional.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package de.danoeh.antennapod.core.util;
+
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+// AntennaPod's stripped-down version of Java/Android platform's java.util.Optional
+// so that it can be used on lower API level (API level 14)
+
+// Android-changed: removed ValueBased paragraph.
+/**
+ * A container object which may or may not contain a non-null value.
+ * If a value is present, {@code isPresent()} will return {@code true} and
+ * {@code get()} will return the value.
+ *
+ * <p>Additional methods that depend on the presence or absence of a contained
+ * value are provided, such as {@link #orElse(java.lang.Object) orElse()}
+ * (return a default value if value not present) and
+ * {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
+ * of code if the value is present).
+ *
+ * @since 1.8
+ */
+public final class Optional<T> {
+ /**
+ * Common instance for {@code empty()}.
+ */
+ private static final Optional<?> EMPTY = new Optional<>();
+
+ /**
+ * If non-null, the value; if null, indicates no value is present
+ */
+ private final T value;
+
+ /**
+ * Constructs an empty instance.
+ *
+ * @implNote Generally only one empty instance, {@link Optional#EMPTY},
+ * should exist per VM.
+ */
+ private Optional() {
+ this.value = null;
+ }
+
+ /**
+ * Returns an empty {@code Optional} instance. No value is present for this
+ * Optional.
+ *
+ * @apiNote Though it may be tempting to do so, avoid testing if an object
+ * is empty by comparing with {@code ==} against instances returned by
+ * {@code Option.empty()}. There is no guarantee that it is a singleton.
+ * Instead, use {@link #isPresent()}.
+ *
+ * @param <T> Type of the non-existent value
+ * @return an empty {@code Optional}
+ */
+ public static<T> Optional<T> empty() {
+ @SuppressWarnings("unchecked")
+ Optional<T> t = (Optional<T>) EMPTY;
+ return t;
+ }
+
+ /**
+ * Constructs an instance with the value present.
+ *
+ * @param value the non-null value to be present
+ * @throws NullPointerException if value is null
+ */
+ private Optional(T value) {
+ this.value = Objects.requireNonNull(value);
+ }
+
+ /**
+ * Returns an {@code Optional} with the specified present non-null value.
+ *
+ * @param <T> the class of the value
+ * @param value the value to be present, which must be non-null
+ * @return an {@code Optional} with the value present
+ * @throws NullPointerException if value is null
+ */
+ public static <T> Optional<T> of(T value) {
+ return new Optional<>(value);
+ }
+
+ /**
+ * Returns an {@code Optional} describing the specified value, if non-null,
+ * otherwise returns an empty {@code Optional}.
+ *
+ * @param <T> the class of the value
+ * @param value the possibly-null value to describe
+ * @return an {@code Optional} with a present value if the specified value
+ * is non-null, otherwise an empty {@code Optional}
+ */
+ public static <T> Optional<T> ofNullable(T value) {
+ return value == null ? empty() : of(value);
+ }
+
+ /**
+ * If a value is present in this {@code Optional}, returns the value,
+ * otherwise throws {@code NoSuchElementException}.
+ *
+ * @return the non-null value held by this {@code Optional}
+ * @throws NoSuchElementException if there is no value present
+ *
+ * @see Optional#isPresent()
+ */
+ public T get() {
+ if (value == null) {
+ throw new NoSuchElementException("No value present");
+ }
+ return value;
+ }
+
+ /**
+ * Return {@code true} if there is a value present, otherwise {@code false}.
+ *
+ * @return {@code true} if there is a value present, otherwise {@code false}
+ */
+ public boolean isPresent() {
+ return value != null;
+ }
+
+
+ /**
+ * Return the value if present, otherwise return {@code other}.
+ *
+ * @param other the value to be returned if there is no value present, may
+ * be null
+ * @return the value, if present, otherwise {@code other}
+ */
+ public T orElse(T other) {
+ return value != null ? value : other;
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this Optional. The
+ * other object is considered equal if:
+ * <ul>
+ * <li>it is also an {@code Optional} and;
+ * <li>both instances have no value present or;
+ * <li>the present values are "equal to" each other via {@code equals()}.
+ * </ul>
+ *
+ * @param obj an object to be tested for equality
+ * @return {code true} if the other object is "equal to" this object
+ * otherwise {@code false}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Optional)) {
+ return false;
+ }
+
+ Optional<?> other = (Optional<?>) obj;
+ return (value == other.value) || (value != null && value.equals(other.value));
+ }
+
+ /**
+ * Returns the hash code value of the present value, if any, or 0 (zero) if
+ * no value is present.
+ *
+ * @return hash code value of the present value or 0 if no value is present
+ */
+ @Override
+ public int hashCode() {
+ return value != null ? value.hashCode() : 0;
+ }
+
+ /**
+ * Returns a non-empty string representation of this Optional suitable for
+ * debugging. The exact presentation format is unspecified and may vary
+ * between implementations and versions.
+ *
+ * @implSpec If a value is present the result must include its string
+ * representation in the result. Empty and present Optionals must be
+ * unambiguously differentiable.
+ *
+ * @return the string representation of this instance
+ */
+ @Override
+ public String toString() {
+ return value != null
+ ? String.format("Optional[%s]", value)
+ : "Optional.empty";
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
index 7377b202d..9408be348 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueAccess.java
@@ -58,19 +58,4 @@ public abstract class QueueAccess {
};
}
- public static QueueAccess NotInQueueAccess() {
- return new QueueAccess() {
- @Override
- public boolean contains(long id) {
- return false;
- }
-
- @Override
- public boolean remove(long id) {
- return false;
- }
- };
-
- }
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java
index ee306a401..d1818aeef 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java
@@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit;
* Media file should be "rewinded" x seconds after user resumes the playback.
*/
public class RewindAfterPauseUtils {
+ private RewindAfterPauseUtils(){}
public static final long ELAPSED_TIME_FOR_SHORT_REWIND = TimeUnit.MINUTES.toMillis(1);
public static final long ELAPSED_TIME_FOR_MEDIUM_REWIND = TimeUnit.HOURS.toMillis(1);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
index 1ef81bf64..3a6bf5755 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/StorageUtils.java
@@ -14,6 +14,8 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
* Utility functions for handling storage errors
*/
public class StorageUtils {
+ private StorageUtils(){}
+
private static final String TAG = "StorageUtils";
public static boolean storageAvailable() {
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..14f091249 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,17 +1,25 @@
package de.danoeh.antennapod.core.util;
+import android.content.Context;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
import android.util.Log;
+import android.util.TypedValue;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
public class ThemeUtils {
+ private ThemeUtils(){}
+
private static final String TAG = "ThemeUtils";
public static int getSelectionBackgroundColor() {
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 +28,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/URLChecker.java b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
index 40faa1dd2..ffc6a6e28 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.util;
import android.net.Uri;
+import android.support.annotation.NonNull;
import android.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
@@ -29,7 +30,7 @@ public final class URLChecker {
* @param url The url which is going to be prepared
* @return The prepared url
*/
- public static String prepareURL(String url) {
+ public static String prepareURL(@NonNull String url) {
url = url.trim();
if (url.startsWith("feed://")) {
if (BuildConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://");
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
index ad723c685..1629f4aaf 100644
--- 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
@@ -11,12 +11,17 @@ 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;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.service.FeedUpdateJobService;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.FeedUpdateUtils;
+
public class AutoUpdateManager {
private static final int JOB_ID_FEED_UPDATE = 42;
private static final String TAG = "AutoUpdateManager";
@@ -152,4 +157,29 @@ public class AutoUpdateManager {
updateIntent);
Log.d(TAG, "Changed alarm to new time of day " + alarm.get(Calendar.HOUR_OF_DAY) + ":" + alarm.get(Calendar.MINUTE));
}
+
+ /*
+ * Checks if the app should refresh all feeds, i.e. if the last auto refresh failed.
+ *
+ * The feeds are only refreshed if an update interval or time of day is set and the last
+ * (successful) refresh was before the last interval or more than a day ago, respectively.
+ *
+ */
+ public static void checkShouldRefreshFeeds(Context context) {
+ long interval = 0;
+ if(UserPreferences.getUpdateInterval() > 0) {
+ interval = UserPreferences.getUpdateInterval();
+ } else if(UserPreferences.getUpdateTimeOfDay().length > 0){
+ interval = TimeUnit.DAYS.toMillis(1);
+ }
+ if(interval == 0) { // auto refresh is disabled
+ return;
+ }
+ long lastRefresh = DBTasks.getLastRefreshAllFeedsTimeMillis(context);
+ Log.d(TAG, "last refresh: " + Converter.getDurationStringLocalized(context,
+ System.currentTimeMillis() - lastRefresh) + " ago");
+ if(lastRefresh <= System.currentTimeMillis() - interval) {
+ FeedUpdateUtils.startAutoUpdate(context, null);
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java b/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java
index ecb641dad..3000e2fa4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java
@@ -12,11 +12,6 @@ public class MediaFileNotFoundException extends Exception {
this.media = media;
}
- public MediaFileNotFoundException(FeedMedia media) {
- super();
- this.media = media;
- }
-
public FeedMedia getMedia() {
return media;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
index 45cb56988..d4d5843d2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java
@@ -11,7 +11,9 @@ import de.danoeh.antennapod.core.BuildConfig;
/** Ensures that only one instance of the FlattrService class exists at a time */
class FlattrServiceCreator {
- private static final String TAG = "FlattrServiceCreator";
+ private FlattrServiceCreator(){}
+
+ public static final String TAG = "FlattrServiceCreator";
private static volatile FlattrService flattrService;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
index 313d1c2f2..919fc82f2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
@@ -1,8 +1,6 @@
package de.danoeh.antennapod.core.util.flattr;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
@@ -36,6 +34,8 @@ import de.danoeh.antennapod.core.storage.DBWriter;
*/
public class FlattrUtils {
+ private FlattrUtils(){}
+
private static final String TAG = "FlattrUtils";
private static final String HOST_NAME = "de.danoeh.antennapod";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
index 386f46724..6e5c3e84b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/MoreContentListFooterUtil.java
@@ -27,8 +27,8 @@ public class MoreContentListFooterUtil {
}
public void setLoadingState(boolean newState) {
- final ImageView imageView = (ImageView) root.findViewById(R.id.imgExpand);
- final ProgressBar progressBar = (ProgressBar) root.findViewById(R.id.progBar);
+ final ImageView imageView = root.findViewById(R.id.imgExpand);
+ final ProgressBar progressBar = root.findViewById(R.id.progBar);
if (newState) {
imageView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
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
index 1c42364ea..52a43aab2 100644
--- 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
@@ -6,6 +6,7 @@ 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 {
@@ -41,6 +42,7 @@ public class NotificationUtils {
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));
+ mChannel.setShowBadge(false);
return mChannel;
}
@@ -49,6 +51,7 @@ public class NotificationUtils {
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));
+ mChannel.setShowBadge(false);
return mChannel;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
index 846733882..2b3f38841 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
@@ -21,18 +21,12 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
private final SharedPreferences.OnSharedPreferenceChangeListener sonicListener =
(sharedPreferences, key) -> {
- if (key.equals(UserPreferences.PREF_SONIC)) {
+ if (key.equals(UserPreferences.PREF_MEDIA_PLAYER)) {
checkMpi();
}
};
@Override
- public void setScreenOnWhilePlaying(boolean screenOn) {
- Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
- throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
- }
-
- @Override
public void setDisplay(SurfaceHolder sh) {
if (sh != null) {
Log.e(TAG, "Setting display not supported in Audio Player");
@@ -40,10 +34,13 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
}
}
- @Override
- public void setVideoScalingMode(int mode) {
- throw new UnsupportedOperationException("Setting scaling mode is not supported in Audio Player");
- }
+ @Override
+ public void setPlaybackParams(float speed, boolean skipSilence) {
+ if(canSetSpeed()) {
+ setPlaybackSpeed(speed);
+ }
+ //Default player does not support silence skipping
+ }
@Override
protected boolean useSonic() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
index aba395ec1..c2b768ea8 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java
@@ -6,13 +6,11 @@ import android.view.SurfaceHolder;
import java.io.IOException;
public interface IPlayer {
- boolean canSetPitch();
boolean canSetSpeed();
boolean canDownmix();
- float getCurrentPitchStepsAdjustment();
int getCurrentPosition();
@@ -20,20 +18,12 @@ public interface IPlayer {
int getDuration();
- float getMaxSpeedMultiplier();
-
- float getMinSpeedMultiplier();
-
- boolean isLooping();
-
boolean isPlaying();
void pause();
void prepare() throws IllegalStateException, IOException;
- void prepareAsync();
-
void release();
void reset();
@@ -42,22 +32,12 @@ public interface IPlayer {
void setAudioStreamType(int streamtype);
- void setScreenOnWhilePlaying(boolean screenOn);
-
void setDataSource(String path) throws IllegalStateException, IOException,
IllegalArgumentException, SecurityException;
void setDisplay(SurfaceHolder sh);
- void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
-
- void setLooping(boolean looping);
-
- void setPitchStepsAdjustment(float pitchSteps);
-
- void setPlaybackPitch(float f);
-
- void setPlaybackSpeed(float f);
+ void setPlaybackParams(float speed, boolean skipSilence);
void setDownmix(boolean enable);
@@ -67,7 +47,5 @@ public interface IPlayer {
void stop();
- void setVideoScalingMode(int mode);
-
void setWakeMode(Context context, int mode);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
index a576dd497..b04c02075 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/MediaPlayerError.java
@@ -7,6 +7,7 @@ import de.danoeh.antennapod.core.R;
/** Utility class for MediaPlayer errors. */
public class MediaPlayerError {
+ private MediaPlayerError(){}
/** Get a human-readable string for a specific error code. */
public static String getErrorString(Context context, int code) {
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 ff7f5b79d..da9b96430 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
@@ -176,6 +176,8 @@ public interface Playable extends Parcelable,
* Provides utility methods for Playable objects.
*/
class PlayableUtils {
+ private PlayableUtils(){}
+
private static final String TAG = "PlayableUtils";
/**
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 a3f02d5cc..6498b9ff1 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
@@ -7,15 +7,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.media.MediaPlayer;
import android.os.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;
@@ -24,10 +20,8 @@ import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
-import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
@@ -41,11 +35,14 @@ import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.Optional;
import de.danoeh.antennapod.core.util.playback.Playable.PlayableUtils;
-import rx.Observable;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
+import io.reactivex.Maybe;
+import io.reactivex.MaybeOnSubscribe;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
/**
* Communicates with the playback service. GUI classes should use this class to
@@ -73,7 +70,8 @@ public abstract class PlaybackController {
private boolean released = false;
private boolean initialized = false;
- private Subscription serviceBinder;
+ private Disposable serviceBinder;
+ private Disposable mediaLoader;
/**
* True if controller should reinit playback service if 'pause' button is
@@ -106,6 +104,7 @@ public abstract class PlaybackController {
}
private synchronized void initServiceRunning() {
+ Log.v(TAG, "initServiceRunning()");
if (initialized) {
return;
}
@@ -148,7 +147,7 @@ public abstract class PlaybackController {
}
if(serviceBinder != null) {
- serviceBinder.unsubscribe();
+ serviceBinder.dispose();
}
try {
activity.unbindService(mConnection);
@@ -183,27 +182,20 @@ public abstract class PlaybackController {
private void bindToService() {
Log.d(TAG, "Trying to connect to service");
if (serviceBinder != null) {
- serviceBinder.unsubscribe();
+ serviceBinder.dispose();
}
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
- .subscribeOn(Schedulers.newThread())
+ .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(intent -> {
+ .subscribe(optionalIntent -> {
boolean bound = false;
- if (!PlaybackService.started) {
- if (intent != null) {
- Log.d(TAG, "Calling start service");
- ContextCompat.startForegroundService(activity, intent);
- bound = activity.bindService(intent, mConnection, 0);
- } else {
- status = PlayerStatus.STOPPED;
- setupGUI();
- handleStatus();
- }
+ if (optionalIntent.isPresent()) {
+ Log.d(TAG, "Calling bind service");
+ bound = activity.bindService(optionalIntent.get(), mConnection, 0);
} else {
- Log.d(TAG, "PlaybackService is running, trying to connect without start command.");
- bound = activity.bindService(new Intent(activity, PlaybackService.class),
- mConnection, 0);
+ status = PlayerStatus.STOPPED;
+ setupGUI();
+ handleStatus();
}
Log.d(TAG, "Result for service binding: " + bound);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
@@ -213,24 +205,26 @@ 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.
*/
- @Nullable private Intent getPlayLastPlayedMediaIntent() {
+ @NonNull
+ private Optional<Intent> getPlayLastPlayedMediaIntent() {
Log.d(TAG, "Trying to restore last played media");
Playable media = PlayableUtils.createInstanceFromPreferences(activity);
if (media == null) {
Log.d(TAG, "No last played media found");
- return null;
+ return Optional.empty();
}
+
boolean fileExists = media.localFileAvailable();
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
}
- return new PlaybackServiceStarter(activity, media)
+ return Optional.of(new PlaybackServiceStarter(activity, media)
.startWhenPrepared(false)
.shouldStream(lastIsStream || !fileExists)
- .getIntent();
+ .getIntent());
}
@@ -413,8 +407,8 @@ public abstract class PlaybackController {
pauseResource = res.getResourceId(1, R.drawable.ic_pause_grey600_36dp);
res.recycle();
} else {
- playResource = R.drawable.ic_av_play_circle_outline_80dp;
- pauseResource = R.drawable.ic_av_pause_circle_outline_80dp;
+ playResource = R.drawable.ic_av_play_white_80dp;
+ pauseResource = R.drawable.ic_av_pause_white_80dp;
}
Log.d(TAG, "status: " + status.toString());
@@ -588,7 +582,8 @@ public abstract class PlaybackController {
.startWhenPrepared(true)
.streamIfLastWasStream()
.start();
- Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
+ Log.d(TAG, "Play/Pause button was pressed, but playbackservice was null - " +
+ "it is likely to have been released by Android system. Restarting it.");
return;
}
switch (status) {
@@ -699,7 +694,7 @@ public abstract class PlaybackController {
return org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(activity.getApplicationContext())
|| UserPreferences.useSonic()
|| Build.VERSION.SDK_INT >= 23
- || playbackService != null && playbackService.canSetSpeed();
+ || (playbackService != null && playbackService.canSetSpeed());
}
public void setPlaybackSpeed(float speed) {
@@ -707,6 +702,11 @@ public abstract class PlaybackController {
playbackService.setSpeed(speed);
}
}
+ public void setSkipSilence(boolean skipSilence) {
+ if (playbackService != null) {
+ playbackService.skipSilence(skipSilence);
+ }
+ }
public void setVolume(float leftVolume, float rightVolume) {
if (playbackService != null) {
@@ -760,6 +760,7 @@ public abstract class PlaybackController {
}
public void notifyVideoSurfaceAbandoned() {
+ Log.v(TAG, "notifyVideoSurfaceAbandoned() - hasPlaybackService=" + (playbackService != null));
if (playbackService != null) {
playbackService.notifyVideoSurfaceAbandoned();
}
@@ -780,18 +781,28 @@ 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);
- }
+ Log.v(TAG, "initServiceNotRunning()");
+ mediaLoader = Maybe.create((MaybeOnSubscribe<Playable>) emitter -> {
+ Playable media = getMedia();
+ if (media != null) {
+ emitter.onSuccess(media);
+ } else {
+ emitter.onComplete();
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(media -> {
+ if (media.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_white_80dp);
+ }
+ }, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
/**
@@ -799,7 +810,7 @@ public abstract class PlaybackController {
*/
public class MediaPositionObserver implements Runnable {
- public static final int WAITING_INTERVALL = 1000;
+ static final int WAITING_INTERVALL = 1000;
@Override
public void run() {
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
index 3ba553d12..64cf61457 100644
--- 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
@@ -2,12 +2,15 @@ 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 android.util.Log;
+
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
public class PlaybackServiceStarter {
+ private static final String TAG = "PlaybackServiceStarter";
+
private final Context context;
private final Playable media;
private boolean startWhenPrepared = false;
@@ -64,6 +67,10 @@ public class PlaybackServiceStarter {
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, prepareImmediately);
+ if (media == null) {
+ Log.e(TAG, "getIntent() - media is unexpectedly null. intent:" + launchIntent);
+ }
+
return launchIntent;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
index 34cfe6d05..75229b9cf 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
@@ -7,6 +7,7 @@ import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.TypedValue;
import org.jsoup.Jsoup;
@@ -14,6 +15,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -68,7 +70,7 @@ public class Timeline {
private static final Pattern TIMECODE_LINK_REGEX = Pattern.compile("antennapod://timecode/((\\d+))");
private static final String TIMECODE_LINK = "<a class=\"timecode\" href=\"antennapod://timecode/%d\">%s</a>";
- private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b(?:(?:(([0-9][0-9])):))?(([0-9][0-9])):(([0-9][0-9]))\\b");
+ private static final Pattern TIMECODE_REGEX = Pattern.compile("\\b((\\d+):)?(\\d+):(\\d{2})\\b");
private static final Pattern LINE_BREAK_REGEX = Pattern.compile("<br */?>");
@@ -81,6 +83,7 @@ public class Timeline {
* @param addTimecodes True if this method should add timecode links
* @return The processed HTML string.
*/
+ @NonNull
public String processShownotes(final boolean addTimecodes) {
final Playable playable = (shownotesProvider instanceof Playable) ? (Playable) shownotesProvider : null;
@@ -90,8 +93,8 @@ public class Timeline {
try {
shownotes = shownotesProvider.loadShownotes().call();
} catch (Exception e) {
- e.printStackTrace();
- return null;
+ Log.e(TAG, "processShownotes() - encounters exceptions unexpectedly in load, treat as if no shownotes.", e);
+ shownotes = "";
}
if (TextUtils.isEmpty(shownotes)) {
@@ -127,35 +130,12 @@ public class Timeline {
// apply timecode links
if (addTimecodes) {
- Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
- Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
- for (Element element : elementsWithTimeCodes) {
- Matcher matcherLong = TIMECODE_REGEX.matcher(element.html());
- StringBuffer buffer = new StringBuffer();
- while (matcherLong.find()) {
- String h = matcherLong.group(1);
- String group = matcherLong.group(0);
- int time = (h != null) ? Converter.durationStringLongToMs(group) :
- Converter.durationStringShortToMs(group);
-
- String rep;
- if (playable == null || playable.getDuration() > time) {
- rep = String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
- } else {
- rep = group;
- }
- matcherLong.appendReplacement(buffer, rep);
- }
- matcherLong.appendTail(buffer);
-
- element.html(buffer.toString());
- }
+ addTimecodes(document, playable);
}
return document.toString();
}
-
/**
* Returns true if the given link is a timecode link.
*/
@@ -186,4 +166,69 @@ public class Timeline {
public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
this.shownotesProvider = shownotesProvider;
}
+
+ private void addTimecodes(Document document, final Playable playable) {
+ Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
+ Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
+
+ if (elementsWithTimeCodes.size() == 0) {
+ // No elements with timecodes
+ return;
+ }
+
+ int playableDuration = playable == null ? Integer.MAX_VALUE : playable.getDuration();
+ boolean useHourFormat = true;
+
+ if (playableDuration != Integer.MAX_VALUE) {
+
+ // We need to decide if we are going to treat short timecodes as HH:MM or MM:SS. To do
+ // so we will parse all the short timecodes and see if they fit in the duration. If one
+ // does not we will use MM:SS, otherwise all will be parsed as HH:MM.
+ for (Element element : elementsWithTimeCodes) {
+ Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
+ while (matcherForElement.find()) {
+
+ // We only want short timecodes right now.
+ if (matcherForElement.group(1) == null) {
+ int time = Converter.durationStringShortToMs(matcherForElement.group(0), true);
+
+ // If the parsed timecode is greater then the duration then we know we need to
+ // use the minute format so we are done.
+ if (time > playableDuration) {
+ useHourFormat = false;
+ break;
+ }
+ }
+ }
+
+ if (!useHourFormat) {
+ break;
+ }
+ }
+ }
+
+ for (Element element : elementsWithTimeCodes) {
+
+ Matcher matcherForElement = TIMECODE_REGEX.matcher(element.html());
+ StringBuffer buffer = new StringBuffer();
+
+ while (matcherForElement.find()) {
+ String group = matcherForElement.group(0);
+
+ int time = matcherForElement.group(1) != null
+ ? Converter.durationStringLongToMs(group)
+ : Converter.durationStringShortToMs(group, useHourFormat);
+
+ String replacementText = group;
+ if (time < playableDuration) {
+ replacementText = String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
+ }
+
+ matcherForElement.appendReplacement(buffer, replacementText);
+ }
+
+ matcherForElement.appendTail(buffer);
+ element.html(buffer.toString());
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
index 368379509..f3c1c4f59 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java
@@ -7,11 +7,6 @@ public class VideoPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "VideoPlayer";
@Override
- public boolean canSetPitch() {
- return false;
- }
-
- @Override
public boolean canSetSpeed() {
return false;
}
@@ -22,47 +17,13 @@ public class VideoPlayer extends MediaPlayer implements IPlayer {
}
@Override
- public float getCurrentPitchStepsAdjustment() {
- return 1;
- }
-
- @Override
public float getCurrentSpeedMultiplier() {
return 1;
}
@Override
- public float getMaxSpeedMultiplier() {
- return 1;
- }
-
- @Override
- public float getMinSpeedMultiplier() {
- return 1;
- }
-
- @Override
- public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException {
- Log.e(TAG, "Setting enable speed adjustment unsupported in video player");
- throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player");
- }
-
- @Override
- public void setPitchStepsAdjustment(float pitchSteps) {
- Log.e(TAG, "Setting pitch steps adjustment unsupported in video player");
- throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player");
- }
-
- @Override
- public void setPlaybackPitch(float f) {
- Log.e(TAG, "Setting playback pitch unsupported in video player");
- throw new UnsupportedOperationException("Setting playback pitch unsupported in video player");
- }
-
- @Override
- public void setPlaybackSpeed(float f) {
- Log.e(TAG, "Setting playback speed unsupported in video player");
- throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
+ public void setPlaybackParams(float speed, boolean skipSilence) {
+ //Ignore this for non ExoPlayer implementations
}
@Override