summaryrefslogtreecommitdiff
path: root/core/src/main/java/de
diff options
context:
space:
mode:
authorMartin Fietz <martin.fietz@gmail.com>2018-10-20 21:55:44 +0200
committerMartin Fietz <martin.fietz@gmail.com>2018-10-20 21:55:44 +0200
commit12c58193800a24b575f0c32c8a9fff8c0f2466c2 (patch)
tree94e2ccf034e8f91eafc2977310d91b8108a53674 /core/src/main/java/de
parentbc8e2bb3c1c557cbca72de268ab42f191ed38927 (diff)
parent4ba36b826893efbe14fce9da3126f89c218db82b (diff)
downloadAntennaPod-12c58193800a24b575f0c32c8a9fff8c0f2466c2.zip
Merge branch 'develop'
Diffstat (limited to 'core/src/main/java/de')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java34
-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.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrStatusFetcher.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlSymbols.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java37
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedComponent.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.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.java65
-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.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/AudioCoverFetcher.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java183
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java56
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java128
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java180
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java142
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java12
-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.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java247
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java175
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java378
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java134
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java157
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java292
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java108
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java41
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java640
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java6
-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.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/Namespace.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Converter.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java2
-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.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntList.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntentUtils.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java100
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/Permutor.java17
-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/QueueSorter.java109
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java15
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java155
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrServiceCreator.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java44
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java64
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/PictureInPictureUtil.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/IPlayer.java26
-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.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java118
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java76
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java29
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/VideoPlayer.java38
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java14
135 files changed, 2695 insertions, 2221 deletions
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 53c134598..a42d495ac 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/UpdateManager.java
@@ -14,7 +14,6 @@ import java.io.File;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
@@ -23,11 +22,11 @@ import de.danoeh.antennapod.core.storage.DBWriter;
/*
* This class's job is do perform maintenance tasks whenever the app has been updated
*/
-public class UpdateManager {
+class UpdateManager {
private UpdateManager(){}
- public static final String TAG = UpdateManager.class.getSimpleName();
+ private static final String TAG = UpdateManager.class.getSimpleName();
private static final String PREF_NAME = "app_version";
private static final String KEY_VERSION_CODE = "version_code";
@@ -57,41 +56,18 @@ public class UpdateManager {
}
}
- public static int getStoredVersionCode() {
+ private static int getStoredVersionCode() {
return prefs.getInt(KEY_VERSION_CODE, -1);
}
- public static void setCurrentVersionCode() {
+ private static void setCurrentVersionCode() {
prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply();
}
private static void onUpgrade(final int oldVersionCode, final int newVersionCode) {
- if(oldVersionCode < 1030099) {
- // delete the now obsolete image cache
- // from now on, Glide will handle caching images
- new Thread() {
- public void run() {
- List<Feed> feeds = DBReader.getFeedList();
- for (Feed podcast : feeds) {
- List<FeedItem> episodes = DBReader.getFeedItemList(podcast);
- for (FeedItem episode : episodes) {
- FeedImage image = episode.getImage();
- if (image != null && image.isDownloaded() && image.getFile_url() != null) {
- File imageFile = new File(image.getFile_url());
- if (imageFile.exists()) {
- imageFile.delete();
- }
- image.setFile_url(null); // calls setDownloaded(false)
- DBWriter.setFeedImage(image);
- }
- }
- }
- }
- }.start();
- }
if(oldVersionCode < 1050004) {
if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
- UserPreferences.enableSonic(true);
+ UserPreferences.enableSonic();
}
}
}
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 0f402f44a..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.
- */
-public 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 67c460e78..74693cf21 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
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.asynctask;
-import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
@@ -10,14 +9,16 @@ import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.service.download.DownloadService;
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> {
- Context context;
- ProgressDialog dialog;
- Feed feed;
+ private final Context context;
+ private ProgressDialog dialog;
+ private final Feed feed;
public boolean skipOnCompletion = false;
public FeedRemover(Context context, Feed feed) {
@@ -42,7 +43,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);
}
}
@@ -55,13 +56,8 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
dialog.show();
}
- @SuppressLint("NewApi")
public void executeAsync() {
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- execute();
- }
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
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 d991006e5..f4c99011a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.asynctask;
-import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -11,6 +10,7 @@ import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.shredzone.flattr4j.exception.FlattrException;
import java.util.LinkedList;
@@ -39,7 +39,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
* to flattr something, a notification will be displayed.
*/
public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorker.ExitCode> {
- protected static final String TAG = "FlattrClickWorker";
+ private static final String TAG = "FlattrClickWorker";
private static final int NOTIFICATION_ID = 4;
@@ -176,7 +176,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context), 0);
- Notification notification = new NotificationCompat.Builder(context)
+ Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
.setContentIntent(contentIntent)
.setContentTitle(context.getString(R.string.no_flattr_token_title))
@@ -209,7 +209,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
+ context.getString(R.string.flattr_click_failure_count, failed);
}
- Notification notification = new NotificationCompat.Builder(context)
+ Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
.setContentIntent(contentIntent)
.setContentTitle(title)
@@ -225,12 +225,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
/**
* Starts the FlattrClickWorker as an AsyncTask.
*/
- @TargetApi(11)
public void executeAsync() {
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- execute();
- }
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
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 4c084eaaf..420a60469 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
@@ -18,8 +18,8 @@ import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
*/
public class FlattrStatusFetcher extends Thread {
- protected static final String TAG = "FlattrStatusFetcher";
- protected Context context;
+ private static final String TAG = "FlattrStatusFetcher";
+ private final Context context;
public FlattrStatusFetcher(Context context) {
super();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java
index 2513d1abd..985cabbf8 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrTokenFetcher.java
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.core.asynctask;
-import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
@@ -23,12 +22,12 @@ import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
public class FlattrTokenFetcher extends AsyncTask<Void, Void, AccessToken> {
private static final String TAG = "FlattrTokenFetcher";
- Context context;
- AndroidAuthenticator auth;
- AccessToken token;
- Uri uri;
- ProgressDialog dialog;
- FlattrException exception;
+ private final Context context;
+ private final AndroidAuthenticator auth;
+ private AccessToken token;
+ private final Uri uri;
+ private ProgressDialog dialog;
+ private FlattrException exception;
public FlattrTokenFetcher(Context context, AndroidAuthenticator auth, Uri uri) {
super();
@@ -80,13 +79,8 @@ public class FlattrTokenFetcher extends AsyncTask<Void, Void, AccessToken> {
}
}
- @SuppressLint("NewApi")
public void executeAsync() {
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- execute();
- }
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
index b14803751..c626a8189 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
@@ -15,9 +15,9 @@ public abstract class ConfirmationDialog {
private static final String TAG = ConfirmationDialog.class.getSimpleName();
- protected Context context;
- private int titleId;
- private String message;
+ private final Context context;
+ private final int titleId;
+ private final String message;
private int positiveText;
private int negativeText;
@@ -32,7 +32,7 @@ public abstract class ConfirmationDialog {
this.message = message;
}
- public void onCancelButtonPressed(DialogInterface dialog) {
+ private void onCancelButtonPressed(DialogInterface dialog) {
Log.d(TAG, "Dialog was cancelled");
dialog.dismiss();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java
index d09f6802f..578007561 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java
@@ -11,8 +11,8 @@ public class FavoritesEvent {
ADDED, REMOVED
}
- public final Action action;
- public final FeedItem item;
+ private final Action action;
+ private final FeedItem item;
private FavoritesEvent(Action action, FeedItem item) {
this.action = action;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
index 7ff241456..9db262857 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
@@ -17,7 +17,8 @@ public class FeedItemEvent {
UPDATE, DELETE_MEDIA
}
- @NonNull public final Action action;
+ @NonNull
+ private final Action action;
@NonNull public final List<FeedItem> items;
private FeedItemEvent(Action action, List<FeedItem> items) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java
index 864d0a405..4a591c996 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedMediaEvent.java
@@ -8,8 +8,8 @@ public class FeedMediaEvent {
UPDATE
}
- public final Action action;
- public final FeedMedia media;
+ private final Action action;
+ private final FeedMedia media;
private FeedMediaEvent(Action action, FeedMedia media) {
this.action = action;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java
new file mode 100644
index 000000000..b3241a8b6
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java
@@ -0,0 +1,13 @@
+package de.danoeh.antennapod.core.event;
+
+public class ServiceEvent {
+ public enum Action {
+ SERVICE_STARTED
+ }
+
+ public final Action action;
+
+ public ServiceEvent(Action action) {
+ this.action = action;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java b/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java
index b8807a686..1ca126469 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/export/html/HtmlSymbols.java
@@ -22,7 +22,7 @@ class HtmlSymbols extends CommonSymbols {
static final String ORDERED_LIST = "ol";
static final String LIST_ITEM = "li";
- static String HEADING = "h1";
+ static final String HEADING = "h1";
static final String LINK = "a";
static final String LINK_DESTINATION = "href";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlSymbols.java b/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlSymbols.java
index 40b0e23b8..86091720d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlSymbols.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/export/opml/OpmlSymbols.java
@@ -3,7 +3,7 @@ package de.danoeh.antennapod.core.export.opml;
import de.danoeh.antennapod.core.export.CommonSymbols;
/** Contains symbols for reading and writing OPML documents. */
-public final class OpmlSymbols extends CommonSymbols {
+final class OpmlSymbols extends CommonSymbols {
public static final String OPML = "opml";
static final String OUTLINE = "outline";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
index f221ed32e..f3dfdfdb6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Chapter.java
@@ -7,19 +7,19 @@ import de.danoeh.antennapod.core.storage.PodDBAdapter;
public abstract class Chapter extends FeedComponent {
/** Defines starting point in milliseconds. */
- protected long start;
- protected String title;
- protected String link;
+ long start;
+ String title;
+ String link;
- public Chapter() {
+ Chapter() {
}
- public Chapter(long start) {
+ Chapter(long start) {
super();
this.start = start;
}
- public Chapter(long start, String title, FeedItem item, String link) {
+ Chapter(long start, String title, FeedItem item, String link) {
super();
this.start = start;
this.title = title;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
index 514a79fad..b769eaf55 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
@@ -27,8 +27,8 @@ public class EventDistributor extends Observable {
public static final int DOWNLOAD_HANDLED = 64;
public static final int PLAYER_STATUS_UPDATE = 128;
- private Handler handler;
- private AbstractQueue<Integer> events;
+ private final Handler handler;
+ private final AbstractQueue<Integer> events;
private static EventDistributor instance;
@@ -52,7 +52,7 @@ public class EventDistributor extends Observable {
deleteObserver(el);
}
- public void addEvent(Integer i) {
+ private void addEvent(Integer i) {
events.offer(i);
handler.post(EventDistributor.this::processEventQueue);
}
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 746dd43c4..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,10 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
if (super.compareWithOther(other)) {
return true;
}
- if(other.image != null && !TextUtils.equals(image.download_url, other.image.download_url)) {
- return true;
+ if (other.imageUrl != null) {
+ if (imageUrl == null || !TextUtils.equals(imageUrl, other.imageUrl)) {
+ return true;
+ }
}
if (!TextUtils.equals(feedTitle, other.feedTitle)) {
return true;
@@ -409,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() {
@@ -503,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 90b5e50b7..a3f91b1c9 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
@@ -7,9 +7,9 @@ package de.danoeh.antennapod.core.feed;
*/
public abstract class FeedComponent {
- protected long id;
+ long id;
- public FeedComponent() {
+ FeedComponent() {
super();
}
@@ -26,7 +26,7 @@ public abstract class FeedComponent {
* FeedComponent. This method should only update attributes which where read from
* the feed.
*/
- public void updateFromOther(FeedComponent other) {
+ void updateFromOther(FeedComponent other) {
}
/**
@@ -36,7 +36,7 @@ public abstract class FeedComponent {
*
* @return true if attribute values are different, false otherwise
*/
- public boolean compareWithOther(FeedComponent other) {
+ boolean compareWithOther(FeedComponent other) {
return false;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
index d04d236e4..b790faadf 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
@@ -9,7 +9,7 @@ public class FeedEvent {
FILTER_CHANGED
}
- public final Action action;
+ private final Action action;
public final long feedId;
public FeedEvent(Action action, long feedId) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
index ca9af058b..cc4dd230f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFile.java
@@ -9,9 +9,9 @@ import java.io.File;
*/
public abstract class FeedFile extends FeedComponent {
- protected String file_url;
+ String file_url;
protected String download_url;
- protected boolean downloaded;
+ boolean downloaded;
/**
* Creates a new FeedFile object.
@@ -40,7 +40,7 @@ public abstract class FeedFile extends FeedComponent {
* FeedFile. This method should only update attributes which where read from
* the feed.
*/
- public void updateFromOther(FeedFile other) {
+ void updateFromOther(FeedFile other) {
super.updateFromOther(other);
this.download_url = other.download_url;
}
@@ -52,7 +52,7 @@ public abstract class FeedFile extends FeedComponent {
*
* @return true if attribute values are different, false otherwise
*/
- public boolean compareWithOther(FeedFile other) {
+ boolean compareWithOther(FeedFile other) {
if (super.compareWithOther(other)) {
return true;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java
index 35abb8de6..28161ac9b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java
@@ -9,8 +9,8 @@ public class FeedFilter {
private static final String TAG = "FeedFilter";
- private String includeFilter;
- private String excludeFilter;
+ private final String includeFilter;
+ private final String excludeFilter;
public FeedFilter() {
this("", "");
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 f0c508830..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;
-
- protected String title;
- protected 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 d497d4949..b0a87c885 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -1,9 +1,10 @@
package de.danoeh.antennapod.core.feed;
import android.database.Cursor;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import de.danoeh.antennapod.core.asynctask.ImageResource;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -14,7 +15,6 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -60,7 +60,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public static final int PLAYED = 1;
private String paymentLink;
- private FlattrStatus flattrStatus;
+ private final FlattrStatus flattrStatus;
/**
* Is true if the database contains any chapters that belong to this item. This attribute is only
@@ -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
@@ -88,7 +88,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
/**
* Any tags assigned to this item
*/
- private Set<String> tags = new HashSet<>();
+ private final Set<String> tags = new HashSet<>();
public FeedItem() {
this.state = UNPLAYED;
@@ -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,9 +158,9 @@ 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);
- assert(id > 0);
String title = cursor.getString(indexTitle);
String link = cursor.getString(indexLink);
Date pubDate = new Date(cursor.getLong(indexPubDate));
@@ -171,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;
@@ -213,9 +214,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
chapters = other.chapters;
}
}
- if (image == null) {
- image = other.image;
- }
}
/**
@@ -374,7 +372,15 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
if (contentEncoded == null || description == null) {
DBReader.loadExtraInformationOfFeedItem(FeedItem.this);
}
- return (contentEncoded != null) ? contentEncoded : description;
+ if (TextUtils.isEmpty(contentEncoded)) {
+ return description;
+ } else if (TextUtils.isEmpty(description)) {
+ return contentEncoded;
+ } else if (description.length() > 1.25 * contentEncoded.length()) {
+ return description;
+ } else {
+ return contentEncoded;
+ }
};
}
@@ -382,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 {
@@ -419,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);
- }
+ public String getImageUrl() {
+ return (imageUrl != null) ? imageUrl : feed.getImageUrl();
}
- /**
- * Returns true if this FeedItem has its own image, false otherwise.
- */
- public boolean hasItemImage() {
- return image != null;
- }
-
- /**
- * 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 5eea4f3da..73d2bb34d 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;
@@ -34,7 +35,7 @@ public class FeedMedia extends FeedFile implements Playable {
public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
- public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
+ private static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
/**
* Indicates we've checked on the size of the item via the network
@@ -88,10 +89,10 @@ public class FeedMedia extends FeedFile implements Playable {
this.lastPlayedTime = lastPlayedTime;
}
- public FeedMedia(long id, FeedItem item, int duration, int position,
- long size, String mime_type, String file_url, String download_url,
- boolean downloaded, Date playbackCompletionDate, int played_duration,
- Boolean hasEmbeddedPicture, long lastPlayedTime) {
+ private FeedMedia(long id, FeedItem item, int duration, int position,
+ long size, String mime_type, String file_url, String download_url,
+ boolean downloaded, Date playbackCompletionDate, int played_duration,
+ Boolean hasEmbeddedPicture, long lastPlayedTime) {
this(id, item, duration, position, size, mime_type, file_url, download_url, downloaded,
playbackCompletionDate, played_duration, lastPlayedTime);
this.hasEmbeddedPicture = hasEmbeddedPicture;
@@ -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));
}
@@ -554,15 +555,9 @@ public class FeedMedia extends FeedFile implements Playable {
public Callable<String> loadShownotes() {
return () -> {
if (item == null) {
- item = DBReader.getFeedItem(
- itemID);
- }
- if (item.getContentEncoded() == null || item.getDescription() == null) {
- DBReader.loadExtraInformationOfFeedItem(
- item);
-
+ item = DBReader.getFeedItem(itemID);
}
- return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
+ return item.loadShownotes().call();
};
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
index 66fc4024b..3285ad7cb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
@@ -33,7 +33,7 @@ public class FeedPreferences {
this(feedID, autoDownload, true, auto_delete_action, username, password, new FeedFilter());
}
- public FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, String username, String password, @NonNull FeedFilter filter) {
+ private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, String username, String password, @NonNull FeedFilter filter) {
this.feedID = feedID;
this.autoDownload = autoDownload;
this.keepUpdated = keepUpdated;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java b/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java
index 9aa8d3170..ea8eb7871 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/SearchResult.java
@@ -1,11 +1,11 @@
package de.danoeh.antennapod.core.feed;
public class SearchResult {
- private FeedComponent component;
+ private final FeedComponent component;
/** Additional information (e.g. where it was found) */
private String subtitle;
/** Higher value means more importance */
- private int value;
+ private final int value;
public SearchResult(FeedComponent component, int value, String subtitle) {
super();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java
index 5b54a2d59..5ab9868a6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/VorbisCommentChapter.java
@@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.feed;
-import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException;
-
import java.util.concurrent.TimeUnit;
+import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException;
+
public class VorbisCommentChapter extends Chapter {
public static final int CHAPTERTYPE_VORBISCOMMENT_CHAPTER = 3;
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 8ca9faa0d..3e4f06a12 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
@@ -27,7 +27,7 @@ import okhttp3.Response;
/**
* @see com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
*/
-public class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
+class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
private static final String TAG = ApOkHttpUrlLoader.class.getSimpleName();
@@ -37,7 +37,7 @@ public class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
public static class Factory implements ModelLoaderFactory<String, InputStream> {
private static volatile OkHttpClient internalClient;
- private OkHttpClient client;
+ private final OkHttpClient client;
private static OkHttpClient getInternalClient() {
if (internalClient == null) {
@@ -80,7 +80,7 @@ public class ApOkHttpUrlLoader implements ModelLoader<String, InputStream> {
private final OkHttpClient client;
- public ApOkHttpUrlLoader(OkHttpClient client) {
+ private ApOkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
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 48dadc492..8159a1b3e 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
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.core.glide;
import android.media.MediaMetadataRetriever;
-import android.util.Log;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
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 4459fbd08..3af5e9080 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
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.gpoddernet;
import android.support.annotation.NonNull;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java
index 16f01f0f4..84c085ed2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceBadStatusCodeException.java
@@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.gpoddernet;
-public class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
- int statusCode;
+class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
+ private final int statusCode;
public GpodnetServiceBadStatusCodeException(String message, int statusCode) {
super(message);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java
index ce704f7e3..78ddfc945 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetServiceException.java
@@ -2,10 +2,10 @@ package de.danoeh.antennapod.core.gpoddernet;
public class GpodnetServiceException extends Exception {
- public GpodnetServiceException() {
+ GpodnetServiceException() {
}
- public GpodnetServiceException(String message) {
+ GpodnetServiceException(String message) {
super(message);
}
@@ -13,7 +13,7 @@ public class GpodnetServiceException extends Exception {
super(cause);
}
- public GpodnetServiceException(String message, Throwable cause) {
+ GpodnetServiceException(String message, Throwable cause) {
super(message, cause);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
index 79eb33cb5..faf4264e5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetDevice.java
@@ -4,10 +4,10 @@ import android.support.annotation.NonNull;
public class GpodnetDevice {
- private String id;
- private String caption;
- private DeviceType type;
- private int subscriptions;
+ private final String id;
+ private final String caption;
+ private final DeviceType type;
+ private final int subscriptions;
public GpodnetDevice(@NonNull String id,
String caption,
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 9627ecae6..b76988fd8 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
@@ -131,7 +131,7 @@ public class GpodnetEpisodeAction {
return this.action;
}
- public String getActionString() {
+ private String getActionString() {
return this.action.name().toLowerCase();
}
@@ -199,16 +199,14 @@ public class GpodnetEpisodeAction {
}
public String writeToString() {
- StringBuilder result = new StringBuilder();
- result.append(this.podcast).append("\t");
- result.append(this.episode).append("\t");
- result.append(this.deviceId).append("\t");
- result.append(this.action).append("\t");
- result.append(this.timestamp.getTime()).append("\t");
- result.append(String.valueOf(this.started)).append("\t");
- result.append(String.valueOf(this.position)).append("\t");
- result.append(String.valueOf(this.total));
- return result.toString();
+ return this.podcast + "\t" +
+ this.episode + "\t" +
+ this.deviceId + "\t" +
+ this.action + "\t" +
+ this.timestamp.getTime() + "\t" +
+ String.valueOf(this.started) + "\t" +
+ String.valueOf(this.position) + "\t" +
+ String.valueOf(this.total);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
index 03c33c9a1..b6efab016 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeActionPostResponse.java
@@ -21,9 +21,9 @@ public class GpodnetEpisodeActionPostResponse {
* URLs that should be updated. The key of the map is the original URL, the value of the map
* is the sanitized URL.
*/
- public final Map<String, String> updatedUrls;
+ private final Map<String, String> updatedUrls;
- public GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
+ private GpodnetEpisodeActionPostResponse(long timestamp, Map<String, String> updatedUrls) {
this.timestamp = timestamp;
this.updatedUrls = updatedUrls;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
index 191c0fa39..680dc1042 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetPodcast.java
@@ -3,13 +3,13 @@ package de.danoeh.antennapod.core.gpoddernet.model;
import android.support.annotation.NonNull;
public class GpodnetPodcast {
- private String url;
- private String title;
- private String description;
- private int subscribers;
- private String logoUrl;
- private String website;
- private String mygpoLink;
+ private final String url;
+ private final String title;
+ private final String description;
+ private final int subscribers;
+ private final String logoUrl;
+ private final String website;
+ private final String mygpoLink;
public GpodnetPodcast(@NonNull String url,
@NonNull String title,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
index 6cc9b79a3..0f1961bef 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetSubscriptionChange.java
@@ -5,9 +5,9 @@ import android.support.annotation.NonNull;
import java.util.List;
public class GpodnetSubscriptionChange {
- private List<String> added;
- private List<String> removed;
- private long timestamp;
+ private final List<String> added;
+ private final List<String> removed;
+ private final long timestamp;
public GpodnetSubscriptionChange(@NonNull List<String> added,
@NonNull List<String> removed,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
index 42a31afc5..40543592e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
@@ -16,7 +16,7 @@ public class GpodnetTag implements Parcelable {
this.usage = usage;
}
- protected GpodnetTag(Parcel in) {
+ private GpodnetTag(Parcel in) {
title = in.readString();
tag = in.readString();
usage = in.readInt();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
index 9bd1881e4..9f9c3bd74 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetUploadChangesResponse.java
@@ -22,9 +22,9 @@ public class GpodnetUploadChangesResponse {
* URLs that should be updated. The key of the map is the original URL, the value of the map
* is the sanitized URL.
*/
- public final Map<String, String> updatedUrls;
+ private final Map<String, String> updatedUrls;
- public GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
+ private GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
this.timestamp = timestamp;
this.updatedUrls = updatedUrls;
}
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 1e7ee0f11..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
@@ -7,6 +7,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -28,26 +29,26 @@ public class GpodnetPreferences {
private static final String TAG = "GpodnetPreferences";
private static final String PREF_NAME = "gpodder.net";
- public static final String PREF_GPODNET_USERNAME = "de.danoeh.antennapod.preferences.gpoddernet.username";
- public static final String PREF_GPODNET_PASSWORD = "de.danoeh.antennapod.preferences.gpoddernet.password";
- public static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID";
- public static final String PREF_GPODNET_HOSTNAME = "prefGpodnetHostname";
+ private static final String PREF_GPODNET_USERNAME = "de.danoeh.antennapod.preferences.gpoddernet.username";
+ private static final String PREF_GPODNET_PASSWORD = "de.danoeh.antennapod.preferences.gpoddernet.password";
+ private static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID";
+ private static final String PREF_GPODNET_HOSTNAME = "prefGpodnetHostname";
- public static final String PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
- public static final String PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_episode_actions_sync_timestamp";
- public static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added";
- public static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed";
- public static final String PREF_SYNC_EPISODE_ACTIONS = "de.danoeh.antennapod.preferences.gpoddernet.sync_queued_episode_actions";
+ private static final String PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
+ private static final String PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_episode_actions_sync_timestamp";
+ private static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added";
+ private static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed";
+ private static final String PREF_SYNC_EPISODE_ACTIONS = "de.danoeh.antennapod.preferences.gpoddernet.sync_queued_episode_actions";
public static final String PREF_LAST_SYNC_ATTEMPT_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_attempt_timestamp";
- public static final String PREF_LAST_SYNC_ATTEMPT_RESULT = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_attempt_result";
+ private static final String PREF_LAST_SYNC_ATTEMPT_RESULT = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_attempt_result";
private static String username;
private static String password;
private static String deviceID;
private static String hostname;
- private static ReentrantLock feedListLock = new ReentrantLock();
+ private static final ReentrantLock feedListLock = new ReentrantLock();
private static Set<String> addedFeeds;
private static Set<String> removedFeeds;
@@ -318,9 +319,7 @@ public class GpodnetPreferences {
private static Set<String> readListFromString(String s) {
Set<String> result = new HashSet<>();
- for (String item : s.split(" ")) {
- result.add(item);
- }
+ Collections.addAll(result, s.split(" "));
return result;
}
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 a3eaf187e..5eec32ebc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
@@ -1,17 +1,21 @@
package de.danoeh.antennapod.core.preferences;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
-
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.service.download.ProxyConfig;
+import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import org.json.JSONArray;
import org.json.JSONException;
@@ -20,19 +24,9 @@ import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
-import de.danoeh.antennapod.core.service.download.ProxyConfig;
-import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
-import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
-import de.danoeh.antennapod.core.util.Converter;
-
/**
* Provides access to preferences set by the user in the settings screen. A
* private instance of this class must first be instantiated via
@@ -42,43 +36,44 @@ import de.danoeh.antennapod.core.util.Converter;
public class UserPreferences {
private UserPreferences(){}
- public static final String IMPORT_DIR = "import/";
+ private static final String IMPORT_DIR = "import/";
private static final String TAG = "UserPreferences";
// User Interface
public static final String PREF_THEME = "prefTheme";
public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
- public static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder";
- public static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator";
+ private static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder";
+ private static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator";
public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
- public static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
+ 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";
- public static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport";
+ private static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport";
// Queue
- public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
+ private static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
// Playback
public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
- public static final String PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT = "prefUnpauseOnBluetoothReconnect";
- public static final String PREF_HARDWARE_FOWARD_BUTTON_SKIPS = "prefHardwareForwardButtonSkips";
- public static final String PREF_HARDWARE_PREVIOUS_BUTTON_RESTARTS = "prefHardwarePreviousButtonRestarts";
+ private static final String PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT = "prefUnpauseOnBluetoothReconnect";
+ private static final String PREF_HARDWARE_FOWARD_BUTTON_SKIPS = "prefHardwareForwardButtonSkips";
+ private static final String PREF_HARDWARE_PREVIOUS_BUTTON_RESTARTS = "prefHardwarePreviousButtonRestarts";
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
- public static final String PREF_SKIP_KEEPS_EPISODE = "prefSkipKeepsEpisode";
- public static final String PREF_FAVORITE_KEEPS_EPISODE = "prefFavoriteKeepsEpisode";
- public static final String PREF_AUTO_DELETE = "prefAutoDelete";
+ private static final String PREF_SKIP_KEEPS_EPISODE = "prefSkipKeepsEpisode";
+ private static final String PREF_FAVORITE_KEEPS_EPISODE = "prefFavoriteKeepsEpisode";
+ private static final String PREF_AUTO_DELETE = "prefAutoDelete";
public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
- public static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
- public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
- public static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
+ private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
+ private static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
+ private static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
+ public static final String PREF_VIDEO_BEHAVIOR = "prefVideoBehavior";
// Network
- public static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
+ private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
- public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
+ private static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
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";
@@ -86,36 +81,35 @@ public class UserPreferences {
public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
public static final String PREF_ENABLE_AUTODL_ON_MOBILE = "prefEnableAutoDownloadOnMobile";
- public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
- public static final String PREF_PROXY_TYPE = "prefProxyType";
- public static final String PREF_PROXY_HOST = "prefProxyHost";
- public static final String PREF_PROXY_PORT = "prefProxyPort";
- public static final String PREF_PROXY_USER = "prefProxyUser";
- public static final String PREF_PROXY_PASSWORD = "prefProxyPassword";
+ private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
+ private static final String PREF_PROXY_TYPE = "prefProxyType";
+ private static final String PREF_PROXY_HOST = "prefProxyHost";
+ private static final String PREF_PROXY_PORT = "prefProxyPort";
+ private static final String PREF_PROXY_USER = "prefProxyUser";
+ private static final String PREF_PROXY_PASSWORD = "prefProxyPassword";
// Services
- public static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
- public static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold";
- public static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications";
+ private static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
+ private static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold";
+ private static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications";
// Other
- public static final String PREF_DATA_FOLDER = "prefDataFolder";
+ private static final String PREF_DATA_FOLDER = "prefDataFolder";
public static final String PREF_IMAGE_CACHE_SIZE = "prefImageCacheSize";
// Mediaplayer
- public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ public static final String PREF_MEDIA_PLAYER = "prefMediaPlayer";
+ private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
private static final String PREF_REWIND_SECS = "prefRewindSecs";
- public static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
- public static final String IMAGE_CACHE_DEFAULT_VALUE = "100";
- public static final int IMAGE_CACHE_SIZE_MINIMUM = 20;
- public static final String PREF_LEFT_VOLUME = "prefLeftVolume";
- public static final String PREF_RIGHT_VOLUME = "prefRightVolume";
+ private static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
+ private static final String IMAGE_CACHE_DEFAULT_VALUE = "100";
+ private static final int IMAGE_CACHE_SIZE_MINIMUM = 20;
+ private static final String PREF_LEFT_VOLUME = "prefLeftVolume";
+ private static final String PREF_RIGHT_VOLUME = "prefRightVolume";
// Experimental
- public static final String PREF_SONIC = "prefSonic";
- public static final String PREF_STEREO_TO_MONO = "PrefStereoToMono";
- public static final String PREF_NORMALIZER = "prefNormalizer";
+ private static final String PREF_STEREO_TO_MONO = "PrefStereoToMono";
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;
@@ -125,10 +119,9 @@ public class UserPreferences {
private static final int NOTIFICATION_BUTTON_REWIND = 0;
private static final int NOTIFICATION_BUTTON_FAST_FORWARD = 1;
private static final int NOTIFICATION_BUTTON_SKIP = 2;
- private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
+ 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;
@@ -167,6 +160,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;
}
@@ -183,8 +178,8 @@ public class UserPreferences {
String.valueOf(NOTIFICATION_BUTTON_SKIP)),
",");
List<Integer> notificationButtons = new ArrayList<>();
- for (int i=0; i<buttons.length; i++) {
- notificationButtons.add(Integer.parseInt(buttons[i]));
+ for (String button : buttons) {
+ notificationButtons.add(Integer.parseInt(button));
}
return notificationButtons;
}
@@ -514,9 +509,8 @@ public class UserPreferences {
.apply();
}
- public static void setVolume(int leftVolume, int rightVolume) {
- assert(0 <= leftVolume && leftVolume <= 100);
- assert(0 <= rightVolume && rightVolume <= 100);
+ public static void setVolume(@IntRange(from = 0, to = 100) int leftVolume,
+ @IntRange(from = 0, to = 100) int rightVolume) {
prefs.edit()
.putInt(PREF_LEFT_VOLUME, leftVolume)
.putInt(PREF_RIGHT_VOLUME, rightVolume)
@@ -604,6 +598,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;
}
@@ -643,13 +639,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("exoplayer");
+ }
+
+ public static void enableSonic() {
+ prefs.edit().putString(PREF_MEDIA_PLAYER, "sonic").apply();
}
public static boolean stereoToMono() {
@@ -662,6 +660,14 @@ public class UserPreferences {
.apply();
}
+ public static VideoBackgroundBehavior getVideoBackgroundBehavior() {
+ switch (prefs.getString(PREF_VIDEO_BEHAVIOR, "stop")) {
+ case "stop": return VideoBackgroundBehavior.STOP;
+ case "pip": return VideoBackgroundBehavior.PICTURE_IN_PICTURE;
+ case "continue": return VideoBackgroundBehavior.CONTINUE_PLAYING;
+ default: return VideoBackgroundBehavior.STOP;
+ }
+ }
public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
int cleanupValue = Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
@@ -773,61 +779,18 @@ public class UserPreferences {
int[] timeOfDay = getUpdateTimeOfDay();
Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
if (timeOfDay.length == 2) {
- restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]);
+ AutoUpdateManager.restartUpdateTimeOfDayAlarm(context, timeOfDay[0], timeOfDay[1]);
} else {
long milliseconds = getUpdateInterval();
long startTrigger = milliseconds;
if (now) {
startTrigger = TimeUnit.SECONDS.toMillis(10);
}
- restartUpdateIntervalAlarm(startTrigger, milliseconds);
+ AutoUpdateManager.restartUpdateIntervalAlarm(context, startTrigger, milliseconds);
}
}
/**
- * Sets the interval in which the feeds are refreshed automatically
- */
- public static void restartUpdateIntervalAlarm(long triggerAtMillis, long intervalMillis) {
- Log.d(TAG, "Restarting update alarm.");
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(context, FeedUpdateReceiver.class);
- PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
- alarmManager.cancel(updateIntent);
- if (intervalMillis > 0) {
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + triggerAtMillis,
- updateIntent);
- Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
- } else {
- Log.d(TAG, "Automatic update was deactivated");
- }
- }
-
- /**
- * Sets time of day the feeds are refreshed automatically
- */
- public static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) {
- Log.d(TAG, "Restarting update alarm.");
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
- new Intent(context, FeedUpdateReceiver.class), 0);
- alarmManager.cancel(updateIntent);
-
- Calendar now = Calendar.getInstance();
- Calendar alarm = (Calendar)now.clone();
- alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
- alarm.set(Calendar.MINUTE, minute);
- if (alarm.before(now) || alarm.equals(now)) {
- alarm.add(Calendar.DATE, 1);
- }
- Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
- alarmManager.set(AlarmManager.RTC_WAKEUP,
- alarm.getTimeInMillis(),
- updateIntent);
- Log.d(TAG, "Changed alarm to new time of day " + hoursOfDay + ":" + minute);
- }
-
- /**
* Reads episode cache size as it is saved in the episode_cache_size_values array.
*/
public static int readEpisodeCacheSize(String valueFromPrefs) {
@@ -840,4 +803,8 @@ public class UserPreferences {
public static boolean isCastEnabled() {
return prefs.getBoolean(PREF_CAST_ENABLED, false);
}
+
+ public enum VideoBackgroundBehavior {
+ STOP, PICTURE_IN_PICTURE, CONTINUE_PLAYING
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index 9bbeb7c88..05e12f6df 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -7,8 +7,7 @@ import android.util.Log;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.FeedUpdateUtils;
/**
* Refreshes all feeds when it receives an intent
@@ -21,11 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent");
ClientConfig.initialize(context);
- if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
- DBTasks.refreshAllFeeds(context, null);
- } else {
- Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
- }
+ FeedUpdateUtils.startAutoUpdate(context, null);
UserPreferences.restartUpdateAlarm(false);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
index 9b4b91151..b191dbf8b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.KeyEvent;
@@ -29,7 +30,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
Intent serviceIntent = new Intent(context, PlaybackService.class);
serviceIntent.putExtra(EXTRA_KEYCODE, event.getKeyCode());
serviceIntent.putExtra(EXTRA_SOURCE, event.getSource());
- context.startService(serviceIntent);
+ ContextCompat.startForegroundService(context, serviceIntent);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
new file mode 100644
index 000000000..edc2ea3e0
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/PlayerWidget.java
@@ -0,0 +1,56 @@
+package de.danoeh.antennapod.core.receiver;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
+
+import java.util.Arrays;
+
+
+public class PlayerWidget extends AppWidgetProvider {
+ private static final String TAG = "PlayerWidget";
+ private static final String PREFS_NAME = "PlayerWidgetPrefs";
+ private static final String KEY_ENABLED = "WidgetEnabled";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive");
+ super.onReceive(context, intent);
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ super.onEnabled(context);
+ Log.d(TAG, "Widget enabled");
+ setEnabled(context, true);
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]");
+ PlayerWidgetJobService.updateWidget(context);
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ super.onDisabled(context);
+ Log.d(TAG, "Widget disabled");
+ setEnabled(context, false);
+ }
+
+ public static boolean isEnabled(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_ENABLED, false);
+ }
+
+ private void setEnabled(Context context, boolean enabled) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(KEY_ENABLED, enabled).apply();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
new file mode 100644
index 000000000..55a8d6b86
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateJobService.java
@@ -0,0 +1,34 @@
+package de.danoeh.antennapod.core.service;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.FeedUpdateUtils;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class FeedUpdateJobService extends JobService {
+ private static final String TAG = "FeedUpdateJobService";
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.d(TAG, "Job started");
+ ClientConfig.initialize(getApplicationContext());
+
+ FeedUpdateUtils.startAutoUpdate(getApplicationContext(), () -> {
+ UserPreferences.restartUpdateAlarm(false);
+ jobFinished(params, false); // needsReschedule = false
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return true;
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
index e9312b929..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
@@ -3,11 +3,11 @@ package de.danoeh.antennapod.core.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.os.IBinder;
+import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.SafeJobIntentService;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -15,6 +15,7 @@ import android.util.Pair;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
@@ -37,30 +38,39 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
/**
* Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument.
* This class also provides static methods for starting the GpodnetSyncService.
*/
-public class GpodnetSyncService extends Service {
+public class GpodnetSyncService extends SafeJobIntentService {
+
private static final String TAG = "GpodnetSyncService";
private static final long WAIT_INTERVAL = 5000L;
- public static final String ARG_ACTION = "action";
+ private static final String ARG_ACTION = "action";
- public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
- public static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
- public static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
+ private static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
+ private static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
+ private static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
private GpodnetService service;
- private boolean syncSubscriptions = false;
- private boolean syncActions = false;
+ private static final AtomicInteger syncActionCount = new AtomicInteger(0);
+ private static boolean syncSubscriptions = false;
+ private static boolean syncActions = false;
+
+ private static final int JOB_ID = -17000;
+
+ private static void enqueueWork(Context context, Intent intent) {
+ enqueueWork(context, GpodnetSyncService.class, JOB_ID, intent);
+ }
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
+ protected void onHandleWork(@NonNull Intent intent) {
+ final String action = intent.getStringExtra(ARG_ACTION);
if (action != null) {
switch(action) {
case ACTION_SYNC:
@@ -78,24 +88,20 @@ public class GpodnetSyncService extends Service {
}
if(syncSubscriptions || syncActions) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
- syncWaiterThread.restart();
+ int syncActionId = syncActionCount.incrementAndGet();
+ try {
+ Thread.sleep(WAIT_INTERVAL);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if (syncActionId == syncActionCount.get()) {
+ // onHandleWork was not called again in the meantime
+ sync();
+ }
}
} else {
Log.e(TAG, "Received invalid intent: action argument is null");
}
- return START_FLAG_REDELIVERY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.d(TAG, "onDestroy");
- syncWaiterThread.interrupt();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
}
private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
@@ -109,6 +115,7 @@ public class GpodnetSyncService extends Service {
private synchronized void sync() {
if (!GpodnetPreferences.loggedIn() || !NetworkUtils.networkAvailable()) {
+ stopForeground(true);
stopSelf();
return;
}
@@ -125,7 +132,6 @@ public class GpodnetSyncService extends Service {
}
syncActions = false;
}
- stopSelf();
}
private synchronized void syncSubscriptionChanges() {
@@ -222,14 +228,12 @@ public class GpodnetSyncService extends Service {
} catch (GpodnetServiceException e) {
e.printStackTrace();
updateErrorNotification(e);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
}
}
private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
- List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException {
+ List<GpodnetEpisodeAction> remoteActions) {
if(remoteActions.size() == 0) {
return;
}
@@ -321,7 +325,7 @@ public class GpodnetSyncService extends Service {
}
PendingIntent activityIntent = ClientConfig.gpodnetCallbacks.getGpodnetSyncServiceErrorNotificationPendingIntent(this);
- Notification notification = new NotificationCompat.Builder(this)
+ Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setContentTitle(title)
.setContentText(description)
.setContentIntent(activityIntent)
@@ -333,69 +337,11 @@ public class GpodnetSyncService extends Service {
nm.notify(id, notification);
}
- private WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
- @Override
- public void onWaitCompleted() {
- sync();
- }
- };
-
- private abstract class WaiterThread {
- private long waitInterval;
- private Thread thread;
-
- private WaiterThread(long waitInterval) {
- this.waitInterval = waitInterval;
- reinit();
- }
-
- public abstract void onWaitCompleted();
-
- public void exec() {
- if (!thread.isAlive()) {
- thread.start();
- }
- }
-
- private void reinit() {
- if (thread != null && thread.isAlive()) {
- Log.d(TAG, "Interrupting waiter thread");
- thread.interrupt();
- }
- thread = new Thread() {
- @Override
- public void run() {
- try {
- Thread.sleep(waitInterval);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (!isInterrupted()) {
- synchronized (this) {
- onWaitCompleted();
- }
- }
- }
- };
- }
-
- public void restart() {
- reinit();
- exec();
- }
-
- public void interrupt() {
- if (thread != null && thread.isAlive()) {
- thread.interrupt();
- }
- }
- }
-
public static void sendSyncIntent(Context context) {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
@@ -403,7 +349,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
@@ -411,7 +357,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
- context.startService(intent);
+ enqueueWork(context, intent);
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
new file mode 100644
index 000000000..6dab9a561
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
@@ -0,0 +1,180 @@
+package de.danoeh.antennapod.core.service;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.v4.app.SafeJobIntentService;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.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;
+
+/**
+ * Updates the state of the player widget
+ */
+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, JOB_ID, new Intent(context, PlayerWidgetJobService.class));
+ }
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ if (!PlayerWidget.isEnabled(getApplicationContext())) {
+ return;
+ }
+
+ synchronized (waitForService) {
+ if (PlaybackService.isRunning && playbackService == null) {
+ bindService(new Intent(this, PlaybackService.class), mConnection, 0);
+ while (playbackService == null) {
+ try {
+ waitForService.wait();
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }
+ }
+
+ updateViews();
+
+ if (playbackService != null) {
+ try {
+ unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "IllegalArgumentException when trying to unbind service");
+ }
+ }
+ }
+
+ private void updateViews() {
+
+ ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
+ AppWidgetManager manager = AppWidgetManager.getInstance(this);
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.player_widget);
+ PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this), 0);
+
+ final PendingIntent startAppPending = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ boolean nothingPlaying = false;
+ Playable media;
+ PlayerStatus status;
+ if (playbackService != null) {
+ media = playbackService.getPlayable();
+ status = playbackService.getStatus();
+ } else {
+ media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
+ status = PlayerStatus.STOPPED;
+ }
+
+ if (media != null) {
+ views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
+
+ views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
+
+ String progressString;
+ if (playbackService != null) {
+ progressString = getProgressString(playbackService.getCurrentPosition(), playbackService.getDuration());
+ } else {
+ progressString = getProgressString(media.getPosition(), media.getDuration());
+ }
+
+ if (progressString != null) {
+ views.setViewVisibility(R.id.txtvProgress, View.VISIBLE);
+ views.setTextViewText(R.id.txtvProgress, progressString);
+ }
+
+ if (status == PlayerStatus.PLAYING) {
+ views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp);
+ if (Build.VERSION.SDK_INT >= 15) {
+ views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
+ }
+ } else {
+ views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
+ if (Build.VERSION.SDK_INT >= 15) {
+ views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
+ }
+ }
+ views.setOnClickPendingIntent(R.id.butPlay, createMediaButtonIntent());
+ } else {
+ nothingPlaying = true;
+ }
+
+ if (nothingPlaying) {
+ // start the app if they click anything
+ views.setOnClickPendingIntent(R.id.layout_left, startAppPending);
+ views.setOnClickPendingIntent(R.id.butPlay, startAppPending);
+ views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
+ views.setTextViewText(R.id.txtvTitle,
+ this.getString(R.string.no_media_playing_label));
+ views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
+ }
+
+ manager.updateAppWidget(playerWidget, views);
+ }
+
+ /**
+ * Creates an intent which fakes a mediabutton press
+ */
+ private PendingIntent createMediaButtonIntent() {
+ KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ Intent startingIntent = new Intent(getBaseContext(), MediaButtonReceiver.class);
+ startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
+ startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+
+ return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
+ }
+
+ private String getProgressString(int position, int duration) {
+ if (position > 0 && duration > 0) {
+ return Converter.getDurationStringLong(position) + " / "
+ + Converter.getDurationStringLong(duration);
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Connection to service established");
+ if (service instanceof PlaybackService.LocalBinder) {
+ synchronized (waitForService) {
+ playbackService = ((PlaybackService.LocalBinder) service).getService();
+ waitForService.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ playbackService = null;
+ Log.d(TAG, "Disconnected from service");
+ }
+
+ };
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index 300f57be6..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
@@ -45,10 +45,10 @@ public class AntennapodHttpClient {
private static final String TAG = "AntennapodHttpClient";
- public static final int CONNECTION_TIMEOUT = 30000;
- public static final int READ_TIMEOUT = 30000;
+ private static final int CONNECTION_TIMEOUT = 30000;
+ private static final int READ_TIMEOUT = 30000;
- public static final int MAX_CONNECTIONS = 8;
+ private static final int MAX_CONNECTIONS = 8;
private static volatile OkHttpClient httpClient = null;
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 de91916a9..75c28564e 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
@@ -17,15 +17,15 @@ public class DownloadRequest implements Parcelable {
private String username;
private String password;
private String lastModified;
- private boolean deleteOnFailure;
+ private final boolean deleteOnFailure;
private final long feedfileId;
private final int feedfileType;
private final Bundle arguments;
- protected int progressPercent;
- protected long soFar;
- protected long size;
- protected int statusMsg;
+ private int progressPercent;
+ private long soFar;
+ private long size;
+ private int statusMsg;
public DownloadRequest(@NonNull String destination,
@NonNull String source,
@@ -53,7 +53,7 @@ public class DownloadRequest implements Parcelable {
this(destination, source, title, feedfileId, feedfileType, null, null, true, null);
}
- public DownloadRequest(Builder builder) {
+ private DownloadRequest(Builder builder) {
this.destination = builder.destination;
this.source = builder.source;
this.title = builder.title;
@@ -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;
}
@@ -254,15 +250,15 @@ public class DownloadRequest implements Parcelable {
}
public static class Builder {
- private String destination;
- private String source;
- private String title;
+ private final String destination;
+ private final String source;
+ private final String title;
private String username;
private String password;
private String lastModified;
private boolean deleteOnFailure = false;
- private long feedfileId;
- private int feedfileType;
+ private final long feedfileId;
+ private final int feedfileType;
private Bundle arguments;
public Builder(@NonNull String destination, @NonNull FeedFile item) {
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 0dc5c9db2..4bd2d8f19 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -7,8 +7,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.os.Binder;
import android.os.Build;
@@ -22,8 +20,9 @@ import android.util.Log;
import android.util.Pair;
import android.webkit.URLUtil;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;
import java.io.File;
@@ -57,7 +56,6 @@ import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -128,8 +126,8 @@ public class DownloadService extends Service {
private NotificationCompat.Builder notificationCompatBuilder;
- private int NOTIFICATION_ID = 2;
- private int REPORT_ID = 3;
+ private static final int NOTIFICATION_ID = 2;
+ private static final int REPORT_ID = 3;
/**
* Currently running downloads.
@@ -153,7 +151,7 @@ public class DownloadService extends Service {
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
- private Handler postHandler = new Handler();
+ private final Handler postHandler = new Handler();
private final IBinder mBinder = new LocalBinder();
@@ -163,7 +161,7 @@ public class DownloadService extends Service {
}
}
- private Thread downloadCompletionThread = new Thread() {
+ private final Thread downloadCompletionThread = new Thread() {
private static final String TAG = "downloadCompletionThd";
@Override
@@ -259,6 +257,7 @@ public class DownloadService extends Service {
public void onCreate() {
Log.d(TAG, "Service started");
isRunning = true;
+ PodDBAdapter.getInstance().open(); // Prevent thrashing the database by opening and closing rapidly
handler = new Handler();
reportQueue = Collections.synchronizedList(new ArrayList<>());
downloads = Collections.synchronizedList(new ArrayList<>());
@@ -296,6 +295,7 @@ public class DownloadService extends Service {
setupNotificationBuilders();
requester = DownloadRequester.getInstance();
+ startForeground(NOTIFICATION_ID, updateNotifications());
}
@Override
@@ -337,15 +337,13 @@ public class DownloadService extends Service {
// start auto download in case anything new has shown up
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
+ PodDBAdapter.getInstance().close();
}
private void setupNotificationBuilders() {
- Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.stat_notify_sync);
-
- notificationCompatBuilder = new NotificationCompat.Builder(this)
+ notificationCompatBuilder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOADING)
.setOngoing(true)
.setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
- .setLargeIcon(icon)
.setSmallIcon(R.drawable.stat_notify_sync);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notificationCompatBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
@@ -356,7 +354,7 @@ public class DownloadService extends Service {
/**
* Updates the contents of the service's notifications. Should be called
- * before setupNotificationBuilders.
+ * after setupNotificationBuilders.
*/
private Notification updateNotifications() {
if (notificationCompatBuilder == null) {
@@ -385,7 +383,7 @@ public class DownloadService extends Service {
return null;
}
- private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -424,6 +422,8 @@ public class DownloadService extends Service {
"ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
}
+ writeFileUrl(request);
+
Downloader downloader = getDownloader(request);
if (downloader != null) {
numberOfDownloads.incrementAndGet();
@@ -491,9 +491,7 @@ public class DownloadService extends Service {
if (status.isSuccessful()) {
successfulDownloads++;
} else if (!status.isCancelled()) {
- if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
- createReport = true;
- }
+ createReport = true;
failedDownloads++;
}
}
@@ -501,7 +499,7 @@ public class DownloadService extends Service {
if (createReport) {
Log.d(TAG, "Creating report");
// create notification object
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setTicker(getString(R.string.download_report_title))
.setContentTitle(getString(R.string.download_report_content_title))
.setContentText(
@@ -510,10 +508,6 @@ public class DownloadService extends Service {
successfulDownloads, failedDownloads)
)
.setSmallIcon(R.drawable.stat_notify_sync_error)
- .setLargeIcon(
- BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync_error)
- )
.setContentIntent(
ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this)
)
@@ -533,14 +527,14 @@ public class DownloadService extends Service {
* Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is
* used from a thread other than the main thread.
*/
- void queryDownloadsAsync() {
+ private void queryDownloadsAsync() {
handler.post(DownloadService.this::queryDownloads);
}
/**
* Check if there's something else to download, otherwise stop
*/
- void queryDownloads() {
+ private void queryDownloads() {
Log.d(TAG, numberOfDownloads.get() + " downloads left");
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
@@ -557,14 +551,13 @@ public class DownloadService extends Service {
final String resourceTitle = (downloadRequest.getTitle() != null) ?
downloadRequest.getTitle() : downloadRequest.getSource();
- NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this, NotificationUtils.CHANNEL_ID_USER_ACTION);
builder.setTicker(getText(R.string.authentication_notification_title))
.setContentTitle(getText(R.string.authentication_notification_title))
.setContentText(getText(R.string.authentication_notification_msg))
.setStyle(new NotificationCompat.BigTextStyle().bigText(getText(R.string.authentication_notification_msg)
+ ": " + resourceTitle))
.setSmallIcon(R.drawable.ic_stat_authentication)
- .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_authentication))
.setAutoCancel(true)
.setContentIntent(ClientConfig.downloadServiceCallbacks.getAuthentificationNotificationContentIntent(DownloadService.this, downloadRequest));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -603,14 +596,14 @@ public class DownloadService extends Service {
private class FeedSyncThread extends Thread {
private static final String TAG = "FeedSyncThread";
- private BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<>();
- private CompletionService<Pair<DownloadRequest, FeedHandlerResult>> parserService = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor());
- private ExecutorService dbService = Executors.newSingleThreadExecutor();
+ private final BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<>();
+ private final CompletionService<Pair<DownloadRequest, FeedHandlerResult>> parserService = new ExecutorCompletionService<>(Executors.newSingleThreadExecutor());
+ private final ExecutorService dbService = Executors.newSingleThreadExecutor();
private Future<?> dbUpdateFuture;
private volatile boolean isActive = true;
private volatile boolean isCollectingRequests = false;
- private final long WAIT_TIMEOUT = 3000;
+ private static final long WAIT_TIMEOUT = 3000;
/**
@@ -695,10 +688,6 @@ public class DownloadService extends Service {
Log.d(TAG, "Bundling " + results.size() + " feeds");
- for (Pair<DownloadRequest, FeedHandlerResult> result : results) {
- removeDuplicateImages(result.second.feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet.
- }
-
// Save information of feed in DB
if (dbUpdateFuture != null) {
try {
@@ -765,7 +754,7 @@ public class DownloadService extends Service {
private class FeedParserTask implements Callable<Pair<DownloadRequest, FeedHandlerResult>> {
- private DownloadRequest request;
+ private final DownloadRequest request;
private FeedParserTask(DownloadRequest request) {
this.request = request;
@@ -906,6 +895,42 @@ public class DownloadService extends Service {
}
/**
+ * Creates the destination file and writes FeedMedia File_url directly after starting download
+ * to make it possible to resume download after the service was killed by the system.
+ */
+ private void writeFileUrl(DownloadRequest request) {
+ if (request.getFeedfileType() != FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ return;
+ }
+
+ File dest = new File(request.getDestination());
+ if (!dest.exists()) {
+ try {
+ dest.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to create file");
+ }
+ }
+
+ if (dest.exists()) {
+ Log.d(TAG, "Writing file url");
+ FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
+ if (media == null) {
+ Log.d(TAG, "No media");
+ return;
+ }
+ media.setFile_url(request.getDestination());
+ try {
+ DBWriter.setFeedMedia(media).get();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "writeFileUrl was interrupted");
+ } catch (ExecutionException e) {
+ Log.e(TAG, "ExecutionException in writeFileUrl: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
* Handles failed downloads.
* <p/>
* If the file has been partially downloaded, this handler will set the file_url of the FeedFile to the location
@@ -913,10 +938,10 @@ public class DownloadService extends Service {
* <p/>
* Currently, this handler only handles FeedMedia objects, because Feeds and FeedImages are deleted if the download fails.
*/
- private class FailedDownloadHandler implements Runnable {
+ private static class FailedDownloadHandler implements Runnable {
- private DownloadRequest request;
- private DownloadStatus status;
+ private final DownloadRequest request;
+ private final DownloadStatus status;
FailedDownloadHandler(DownloadStatus status, DownloadRequest request) {
this.request = request;
@@ -929,23 +954,6 @@ public class DownloadService extends Service {
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
} else if (request.isDeleteOnFailure()) {
Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
- } else {
- File dest = new File(request.getDestination());
- if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- Log.d(TAG, "File has been partially downloaded. Writing file url");
- FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
- if (media == null) {
- return;
- }
- media.setFile_url(request.getDestination());
- try {
- DBWriter.setFeedMedia(media).get();
- } catch (InterruptedException e) {
- Log.e(TAG, "FailedDownloadHandler was interrupted");
- } catch (ExecutionException e) {
- Log.e(TAG, "ExecutionException in FailedDownloadHandler: " + e.getMessage());
- }
- }
}
}
}
@@ -955,7 +963,7 @@ public class DownloadService extends Service {
*/
private class MediaHandlerThread implements Runnable {
- private DownloadRequest request;
+ private final DownloadRequest request;
private DownloadStatus status;
MediaHandlerThread(@NonNull DownloadStatus status,
@@ -1071,7 +1079,7 @@ public class DownloadService extends Service {
private long lastPost = 0;
- final Runnable postDownloaderTask = new Runnable() {
+ private final Runnable postDownloaderTask = new Runnable() {
@Override
public void run() {
List<Downloader> list = Collections.unmodifiableList(downloads);
@@ -1089,26 +1097,6 @@ public class DownloadService extends Service {
}
}
- /**
- * Checks if the FeedItems of this feed have images that point to the same URL. If two FeedItems
- * have an image that points to the same URL, the reference of the second item is removed, so
- * that every image reference is unique.
- */
- @VisibleForTesting
- public static void removeDuplicateImages(Feed feed) {
- Set<String> known = new HashSet<>();
- for (FeedItem item : feed.getItems()) {
- String url = item.hasItemImage() ? item.getImage().getDownload_url() : null;
- if (url != null) {
- if (known.contains(url)) {
- item.setImage(null);
- } else {
- known.add(url);
- }
- }
- }
- }
-
private static String compileNotificationString(List<Downloader> downloads) {
List<String> lines = new ArrayList<>(downloads.size());
for (Downloader downloader : downloads) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
index ed2b00dfe..5debc6d05 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java
@@ -19,37 +19,37 @@ public class DownloadStatus {
// ----------------------------------- ATTRIBUTES STORED IN DB
/** Unique id for storing the object in database. */
- protected long id;
+ private long id;
/**
* A human-readable string which is shown to the user so that he can
* identify the download. Should be the title of the item/feed/media or the
* URL if the download has no other title.
*/
- protected String title;
- protected DownloadError reason;
+ private final String title;
+ private DownloadError reason;
/**
* A message which can be presented to the user to give more information.
* Should be null if Download was successful.
*/
- protected String reasonDetailed;
- protected boolean successful;
- protected Date completionDate;
- protected long feedfileId;
+ private String reasonDetailed;
+ private boolean successful;
+ private Date completionDate;
+ private final long feedfileId;
/**
* Is used to determine the type of the feedfile even if the feedfile does
* not exist anymore. The value should be FEEDFILETYPE_FEED,
* FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA
*/
- protected int feedfileType;
+ private final int feedfileType;
// ------------------------------------ NOT STORED IN DB
- protected boolean done;
- protected boolean cancelled;
+ private boolean done;
+ private boolean cancelled;
/** Constructor for restoring Download status entries from DB. */
- public DownloadStatus(long id, String title, long feedfileId,
- int feedfileType, boolean successful, DownloadError reason,
- Date completionDate, String reasonDetailed) {
+ private DownloadStatus(long id, String title, long feedfileId,
+ int feedfileType, boolean successful, DownloadError reason,
+ Date completionDate, String reasonDetailed) {
this.id = id;
this.title = title;
this.done = true;
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 d8042d202..445210d3a 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
@@ -14,14 +14,14 @@ import de.danoeh.antennapod.core.R;
public abstract class Downloader implements Callable<Downloader> {
private static final String TAG = "Downloader";
- protected volatile boolean finished;
+ private volatile boolean finished;
- protected volatile boolean cancelled;
+ volatile boolean cancelled;
- protected DownloadRequest request;
- protected DownloadStatus result;
+ final DownloadRequest request;
+ final DownloadStatus result;
- public Downloader(DownloadRequest request) {
+ Downloader(DownloadRequest request) {
super();
this.request = request;
this.request.setStatusMsg(R.string.download_pending);
@@ -33,7 +33,7 @@ public abstract class Downloader implements Callable<Downloader> {
public final Downloader call() {
WifiManager wifiManager = (WifiManager)
- ClientConfig.applicationCallbacks.getApplicationInstance().getSystemService(Context.WIFI_SERVICE);
+ ClientConfig.applicationCallbacks.getApplicationInstance().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiManager.WifiLock wifiLock = null;
if (wifiManager != null) {
wifiLock = wifiManager.createWifiLock(TAG);
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 b409a419a..8cce02155 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -20,7 +20,6 @@ import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
@@ -50,13 +49,8 @@ public class HttpDownloader extends Downloader {
if (request.isDeleteOnFailure() && fileExists) {
Log.w(TAG, "File already exists");
- if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
- onFail(DownloadError.ERROR_FILE_EXISTS, null);
- return;
- } else {
- onSuccess();
- return;
- }
+ onSuccess();
+ return;
}
OkHttpClient.Builder httpClientBuilder = AntennapodHttpClient.newBuilder();
@@ -93,7 +87,7 @@ public class HttpDownloader extends Downloader {
// add range header if necessary
- if (fileExists) {
+ if (fileExists && destination.length() > 0) {
request.setSoFar(destination.length());
httpReq.addHeader("Range", "bytes=" + request.getSoFar() + "-");
Log.d(TAG, "Adding range header: " + request.getSoFar());
@@ -314,9 +308,9 @@ public class HttpDownloader extends Downloader {
}
}
- private class BasicAuthorizationInterceptor implements Interceptor {
+ private static class BasicAuthorizationInterceptor implements Interceptor {
- private DownloadRequest downloadRequest;
+ private final DownloadRequest downloadRequest;
public BasicAuthorizationInterceptor(DownloadRequest downloadRequest) {
this.downloadRequest = downloadRequest;
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..cc9d2ce2d
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java
@@ -0,0 +1,247 @@
+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 de.danoeh.antennapod.core.util.playback.IPlayer;
+import org.antennapod.audio.MediaPlayer;
+
+
+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(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 setPlaybackSpeed(float v) {
+ PlaybackParameters params = mExoPlayer.getPlaybackParameters();
+ mExoPlayer.setPlaybackParameters(new PlaybackParameters(v, params.pitch));
+ }
+
+ @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 11cd21db5..c7948b157 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;
@@ -11,6 +14,7 @@ import android.view.SurfaceHolder;
import org.antennapod.audio.MediaPlayer;
+import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
@@ -32,7 +36,7 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer;
* Manages the MediaPlayer object of the PlaybackService.
*/
public class LocalPSMP extends PlaybackServiceMediaPlayer {
- public static final String TAG = "LclPlaybackSvcMPlayer";
+ private static final String TAG = "LclPlaybackSvcMPlayer";
private final AudioManager audioManager;
@@ -42,7 +46,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
private volatile boolean stream;
private volatile MediaType mediaType;
- private volatile AtomicBoolean startWhenPrepared;
+ private final AtomicBoolean startWhenPrepared;
private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
private volatile Pair<Integer, Integer> videoSize;
@@ -165,8 +169,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
callback.onMediaChanged(false);
if (stream) {
mediaPlayer.setDataSource(media.getStreamUrl());
- } else {
+ } else if (new File(media.getLocalMediaUrl()).canRead()) {
mediaPlayer.setDataSource(media.getLocalMediaUrl());
+ } else {
+ throw new IOException("Unable to read local file " + media.getLocalMediaUrl());
}
setPlayerStatus(PlayerStatus.INITIALIZED, media);
@@ -199,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");
@@ -256,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) {
@@ -310,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());
}
@@ -444,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();
}
@@ -606,11 +639,30 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
public void shutdown() {
executor.shutdown();
if (mediaPlayer != null) {
+ try {
+ 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.
@@ -663,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());
@@ -692,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);
@@ -710,52 +770,49 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
public void onAudioFocusChange(final int focusChange) {
- executor.submit(new Runnable() {
- @Override
- public void run() {
- playerLock.lock();
-
- // If there is an incoming call, playback should be paused permanently
- TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- final int callState = (tm != null) ? tm.getCallState() : 0;
- Log.i(TAG, "Call state:" + callState);
-
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
- (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
- Log.d(TAG, "Lost audio focus");
- pause(true, false);
- callback.shouldStop();
- } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- Log.d(TAG, "Gained audio focus");
- if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
- resume();
- } else { // we ducked => raise audio level back
- setVolumeSync(UserPreferences.getLeftVolume(),
- UserPreferences.getRightVolume());
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
- if (playerStatus == PlayerStatus.PLAYING) {
- if (!UserPreferences.shouldPauseForFocusLoss()) {
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
- final float DUCK_FACTOR = 0.25f;
- setVolumeSync(DUCK_FACTOR * UserPreferences.getLeftVolume(),
- DUCK_FACTOR * UserPreferences.getRightVolume());
- pausedBecauseOfTransientAudiofocusLoss = false;
- } else {
- Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
- if (playerStatus == PlayerStatus.PLAYING) {
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ executor.submit(() -> {
+ playerLock.lock();
+
+ // If there is an incoming call, playback should be paused permanently
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final int callState = (tm != null) ? tm.getCallState() : 0;
+ Log.i(TAG, "Call state:" + callState);
+
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
+ Log.d(TAG, "Lost audio focus");
+ pause(true, false);
+ callback.shouldStop();
+ } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ Log.d(TAG, "Gained audio focus");
+ if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now
+ resume();
+ } else { // we ducked => raise audio level back
+ setVolumeSync(UserPreferences.getLeftVolume(),
+ UserPreferences.getRightVolume());
+ }
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ if (playerStatus == PlayerStatus.PLAYING) {
+ if (!UserPreferences.shouldPauseForFocusLoss()) {
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ final float DUCK_FACTOR = 0.25f;
+ setVolumeSync(DUCK_FACTOR * UserPreferences.getLeftVolume(),
+ DUCK_FACTOR * UserPreferences.getRightVolume());
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ } else {
+ Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
pause(false, false);
pausedBecauseOfTransientAudiofocusLoss = true;
}
}
- playerLock.unlock();
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ if (playerStatus == PlayerStatus.PLAYING) {
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
}
+ playerLock.unlock();
});
}
};
@@ -784,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;
@@ -880,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);
}
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 01c5f751e..979857381 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -30,12 +30,10 @@ import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.view.InputDeviceCompat;
-import android.support.v7.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.widget.Toast;
@@ -49,6 +47,7 @@ import java.util.List;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.MessageEvent;
+import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -60,11 +59,14 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.IntList;
+import de.danoeh.antennapod.core.util.IntentUtils;
+import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -74,8 +76,6 @@ import de.greenrobot.event.EventBus;
* Controls the MediaPlayer that plays a FeedMedia-file
*/
public class PlaybackService extends MediaBrowserServiceCompat {
- public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE";
- public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE";
/**
* Logging tag
*/
@@ -88,7 +88,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
/**
* True if cast session should disconnect.
*/
- public static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect";
+ private static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect";
/**
* True if media should be streamed.
*/
@@ -198,7 +198,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
/**
* Is true if the service was running, but paused due to headphone disconnect
*/
- public static boolean transientPause = false;
+ private static boolean transientPause = false;
/**
* Is true if a Cast Device is connected to the service.
*/
@@ -263,32 +263,24 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.d(TAG, "Service created.");
isRunning = true;
- registerReceiver(autoStateUpdated, new IntentFilter(
- "com.google.android.gms.car.media.STATUS"));
- registerReceiver(headsetDisconnected, new IntentFilter(
- Intent.ACTION_HEADSET_PLUG));
- registerReceiver(shutdownReceiver, new IntentFilter(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- registerReceiver(bluetoothStateUpdated, new IntentFilter(
- BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED));
- }
- registerReceiver(audioBecomingNoisy, new IntentFilter(
- AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
- ACTION_SKIP_CURRENT_EPISODE));
- registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(
- ACTION_PAUSE_PLAY_CURRENT_EPISODE));
- registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(
- ACTION_RESUME_PLAY_CURRENT_EPISODE));
+ NotificationCompat.Builder notificationBuilder = createBasicNotification();
+ startForeground(NOTIFICATION_ID, notificationBuilder.build());
+
+ registerReceiver(autoStateUpdated, new IntentFilter("com.google.android.gms.car.media.STATUS"));
+ registerReceiver(headsetDisconnected, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
+ registerReceiver(shutdownReceiver, new IntentFilter(ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ registerReceiver(bluetoothStateUpdated, new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED));
+ registerReceiver(audioBecomingNoisy, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
+ registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE));
+ registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(ACTION_RESUME_PLAY_CURRENT_EPISODE));
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback);
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(prefListener);
- ComponentName eventReceiver = new ComponentName(getApplicationContext(),
- MediaButtonReceiver.class);
+ ComponentName eventReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(eventReceiver);
PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -311,7 +303,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
List<MediaSessionCompat.QueueItem> queueItems = new ArrayList<>();
try {
for (FeedItem feedItem : taskManager.getQueue()) {
- if(feedItem.getMedia() != null) {
+ if (feedItem.getMedia() != null) {
MediaDescriptionCompat mediaDescription = feedItem.getMedia().getMediaItem().getDescription();
queueItems.add(new MediaSessionCompat.QueueItem(mediaDescription, feedItem.getId()));
}
@@ -322,14 +314,34 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
flavorHelper.initializeMediaPlayer(PlaybackService.this);
-
mediaSession.setActive(true);
+
+ EventBus.getDefault().post(new ServiceEvent(ServiceEvent.Action.SERVICE_STARTED));
+ }
+
+ private NotificationCompat.Builder createBasicNotification() {
+ final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
+
+ final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
+ PlaybackService.getPlayerActivityIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return new NotificationCompat.Builder(
+ this, NotificationUtils.CHANNEL_ID_PLAYING)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText("Service is running") // Just in case the notification is not updated (should not occur)
+ .setOngoing(false)
+ .setContentIntent(pIntent)
+ .setWhen(0) // we don't need the time
+ .setSmallIcon(smallIcon)
+ .setPriority(NotificationCompat.PRIORITY_MIN);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service is about to be destroyed");
+ stopForeground(true);
isRunning = false;
started = false;
currentMediaType = MediaType.UNKNOWN;
@@ -342,9 +354,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
unregisterReceiver(autoStateUpdated);
unregisterReceiver(headsetDisconnected);
unregisterReceiver(shutdownReceiver);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- unregisterReceiver(bluetoothStateUpdated);
- }
+ unregisterReceiver(bluetoothStateUpdated);
unregisterReceiver(audioBecomingNoisy);
unregisterReceiver(skipCurrentEpisodeReceiver);
unregisterReceiver(pausePlayCurrentEpisodeReceiver);
@@ -354,6 +364,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
mediaPlayer.shutdown();
taskManager.shutdown();
}
+
+ private void stopService() {
+ stopForeground(true);
+ stopSelf();
+ }
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
@@ -379,10 +394,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
.setTitle(feed.getTitle())
.setDescription(feed.getDescription())
.setSubtitle(feed.getCustomTitle());
- if(feed.getImageLocation() != null) {
+ if (feed.getImageLocation() != null) {
builder.setIconUri(Uri.parse(feed.getImageLocation()));
}
- if(feed.getLink() != null) {
+ if (feed.getLink() != null) {
builder.setMediaUri(Uri.parse(feed.getLink()));
}
MediaDescriptionCompat description = builder.build();
@@ -405,13 +420,13 @@ public class PlaybackService extends MediaBrowserServiceCompat {
e.printStackTrace();
}
List<Feed> feeds = DBReader.getFeedList();
- for (Feed feed: feeds) {
+ for (Feed feed : feeds) {
mediaItems.add(createBrowsableMediaItemForFeed(feed));
}
- } else if (parentId.equals(getResources().getString(R.string.queue_label))){
+ } else if (parentId.equals(getResources().getString(R.string.queue_label))) {
// Child List
try {
- for (FeedItem feedItem: taskManager.getQueue()) {
+ for (FeedItem feedItem : taskManager.getQueue()) {
mediaItems.add(feedItem.getMedia().getMediaItem());
}
} catch (InterruptedException e) {
@@ -420,8 +435,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (parentId.startsWith("FeedId:")) {
Long feedId = Long.parseLong(parentId.split(":")[1]);
List<FeedItem> feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId));
- for (FeedItem feedItem: feedItems) {
- if(feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) {
+ for (FeedItem feedItem : feedItems) {
+ if (feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) {
mediaItems.add(feedItem.getMedia().getMediaItem());
}
}
@@ -432,7 +447,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Received onBind event");
- if(intent.getAction() != null && TextUtils.equals(intent.getAction(), MediaBrowserServiceCompat.SERVICE_INTERFACE)) {
+ if (intent.getAction() != null && TextUtils.equals(intent.getAction(), MediaBrowserServiceCompat.SERVICE_INTERFACE)) {
return super.onBind(intent);
} else {
return mBinder;
@@ -449,20 +464,22 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
if (keycode == -1 && playable == null && !castDisconnect) {
Log.e(TAG, "PlaybackService was started with no arguments");
- stopSelf();
- return Service.START_REDELIVER_INTENT;
+ stopService();
+ 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, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE,
- InputDeviceCompat.SOURCE_CLASS_NONE));
- } else if (!flavorHelper.castDisconnect(castDisconnect)) {
+ boolean handled = handleKeycode(keycode, true);
+ if (!handled) {
+ stopService();
+ return Service.START_NOT_STICKY;
+ }
+ } else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
started = true;
boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
true);
@@ -471,20 +488,22 @@ public class PlaybackService extends MediaBrowserServiceCompat {
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 = (Playable) DBReader.getFeedMedia(((FeedMedia)playable).getId());
+ if (playable instanceof FeedMedia) {
+ playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
}
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
}
+ setupNotification(playable);
}
- return Service.START_REDELIVER_INTENT;
+ return Service.START_NOT_STICKY;
}
/**
* Handles media button events
+ * return: keycode was handled
*/
- private void handleKeycode(int keycode, int source) {
+ private boolean handleKeycode(int keycode, boolean notificationButton) {
Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
@@ -500,24 +519,28 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
+ } else if (mediaPlayer.getPlayable() == null) {
+ startPlayingFromPreferences();
}
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_PLAY:
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
mediaPlayer.resume();
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
+ } else if (mediaPlayer.getPlayable() == null) {
+ startPlayingFromPreferences();
}
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
if (status == PlayerStatus.PLAYING) {
mediaPlayer.pause(!UserPreferences.isPersistNotify(), true);
}
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
- if(source == InputDevice.SOURCE_CLASS_NONE ||
+ if (notificationButton ||
UserPreferences.shouldHardwareButtonSkip()) {
// assume the skip command comes from a notification or the lockscreen
// a >| skip button should actually skip
@@ -527,22 +550,22 @@ public class PlaybackService extends MediaBrowserServiceCompat {
// user actually wants to fast-forward
seekDelta(UserPreferences.getFastForwardSecs() * 1000);
}
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
mediaPlayer.seekDelta(UserPreferences.getFastForwardSecs() * 1000);
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- if(UserPreferences.shouldHardwarePreviousButtonRestart()) {
+ if (UserPreferences.shouldHardwarePreviousButtonRestart()) {
// user wants to restart current episode
mediaPlayer.seekTo(0);
} else {
// user wants to rewind current episode
mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
}
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_REWIND:
mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
- break;
+ return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
if (status == PlayerStatus.PLAYING) {
mediaPlayer.pause(true, true);
@@ -550,14 +573,23 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
stopForeground(true); // gets rid of persistent notification
- break;
+ return true;
default:
Log.d(TAG, "Unhandled key code: " + keycode);
if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
String message = String.format(getResources().getString(R.string.unknown_media_key), keycode);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
- break;
+ }
+ return false;
+ }
+
+ private void startPlayingFromPreferences() {
+ Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
+ if (playable != null) {
+ mediaPlayer.playMediaObject(playable, false, true, true);
+ started = true;
+ PlaybackService.this.updateMediaSessionMetadata(playable);
}
}
@@ -579,8 +611,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void notifyVideoSurfaceAbandoned() {
- stopForeground(!UserPreferences.isPersistNotify());
+ mediaPlayer.pause(true, false);
mediaPlayer.resetVideoSurface();
+ setupNotification(getPlayable());
+ stopForeground(!UserPreferences.isPersistNotify());
}
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@@ -614,7 +648,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onWidgetUpdaterTick() {
- updateWidget();
+ PlayerWidgetJobService.updateWidget(getBaseContext());
}
@Override
@@ -626,7 +660,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:
@@ -651,8 +690,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
break;
case STOPPED:
- //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- //stopSelf();
+ //writePlaybackPreferencesNoMediaPlaying();
+ //stopService();
break;
case PLAYING:
@@ -660,8 +699,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
setupNotification(newInfo);
started = true;
// set sleep timer if auto-enabled
- if(newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
- SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
+ if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING &&
+ SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
setSleepTimer(SleepTimerPreferences.timerMillis(), SleepTimerPreferences.shakeToReset(),
SleepTimerPreferences.vibrate());
}
@@ -669,21 +708,20 @@ public class PlaybackService extends MediaBrowserServiceCompat {
case ERROR:
writePlaybackPreferencesNoMediaPlaying();
+ stopService();
break;
}
- Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED);
- // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
- sendBroadcast(statusUpdate);
- updateWidget();
+ IntentUtils.sendLocalBroadcast(getApplicationContext(), ACTION_PLAYER_STATUS_CHANGED);
+ PlayerWidgetJobService.updateWidget(getBaseContext());
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
}
@Override
public void shouldStop() {
- stopSelf();
+ stopService();
}
@Override
@@ -732,7 +770,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
writePlaybackPreferencesNoMediaPlaying();
- stopSelf();
+ stopService();
return true;
}
@@ -804,7 +842,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.e(TAG, "Error handling the queue in order to retrieve the next item", e);
return null;
}
- return (nextItem != null)? nextItem.getMedia() : null;
+ return (nextItem != null) ? nextItem.getMedia() : null;
}
@@ -819,7 +857,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (!isCasting) {
stopForeground(true);
}
- stopWidgetUpdater();
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
@@ -833,7 +870,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
/**
* This method processes the media object after its playback ended, either because it completed
* or because a different media object was selected for playback.
- *
+ * <p>
* Even though these tasks aren't supposed to be resource intensive, a good practice is to
* usually call this method on a background thread.
*
@@ -913,7 +950,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_enabled_label),
- () -> disableSleepTimer()));
+ this::disableSleepTimer));
}
public void disableSleepTimer() {
@@ -1017,22 +1054,17 @@ 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);
}
/**
* Updates the Media Session for the corresponding status.
+ *
* @param playerStatus the current {@link PlayerStatus}
*/
private void updateMediaSession(final PlayerStatus playerStatus) {
@@ -1108,8 +1140,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
// showRewindOnCompactNotification() corresponds to the "Set Lockscreen Buttons"
// Settings in UI.
// Hence, from user perspective, he/she is setting the buttons for Lockscreen
- return ( UserPreferences.showRewindOnCompactNotification() &&
- (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) );
+ return (UserPreferences.showRewindOnCompactNotification() &&
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP));
}
/**
@@ -1161,7 +1193,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PendingIntent.FLAG_UPDATE_CURRENT));
try {
mediaSession.setMetadata(builder.build());
- } catch (OutOfMemoryError e) {
+ } catch (OutOfMemoryError e) {
Log.e(TAG, "Setting media session metadata", e);
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null);
mediaSession.setMetadata(builder.build());
@@ -1182,63 +1214,67 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* Prepares notification and starts the service in the foreground.
*/
private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
- final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
- PlaybackService.getPlayerActivityIntent(this),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ setupNotification(info.playable);
+ }
+ private synchronized void setupNotification(final Playable playable) {
if (notificationSetupThread != null) {
notificationSetupThread.interrupt();
}
+ if (playable == null) {
+ Log.d(TAG, "setupNotification: playable is null");
+ if (!started) {
+ stopService();
+ }
+ return;
+ }
Runnable notificationSetupTask = new Runnable() {
Bitmap icon = null;
@Override
public void run() {
Log.d(TAG, "Starting background work");
- if (android.os.Build.VERSION.SDK_INT >= 11) {
- if (info.playable != null) {
- int iconSize = getResources().getDimensionPixelSize(
- android.R.dimen.notification_large_icon_width);
- try {
- icon = Glide.with(PlaybackService.this)
- .load(info.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);
- }
+
+ if (mediaPlayer == null) {
+ Log.d(TAG, "notificationSetupTask: mediaPlayer is null");
+ if (!started) {
+ stopService();
}
+ return;
}
+
+ 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);
+ }
+
if (icon == null) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()));
}
- if (mediaPlayer == null) {
- return;
- }
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
- final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
- if (!Thread.currentThread().isInterrupted() && started && info.playable != null) {
- String contentText = info.playable.getEpisodeTitle();
- String contentTitle = info.playable.getFeedTitle();
+ if (!Thread.currentThread().isInterrupted() && started) {
+ String contentText = playable.getEpisodeTitle();
+ String contentTitle = playable.getFeedTitle();
Notification notification;
// Builder is v7, even if some not overwritten methods return its parent's v4 interface
- NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(
- PlaybackService.this)
- .setContentTitle(contentTitle)
+ NotificationCompat.Builder notificationBuilder = createBasicNotification();
+ notificationBuilder.setContentTitle(contentTitle)
.setContentText(contentText)
- .setOngoing(false)
- .setContentIntent(pIntent)
- .setLargeIcon(icon)
- .setSmallIcon(smallIcon)
- .setWhen(0) // we don't need the time
- .setPriority(UserPreferences.getNotifyPriority()); // set notification priority
+ .setPriority(UserPreferences.getNotifyPriority())
+ .setLargeIcon(icon); // set notification priority
IntList compactActionList = new IntList();
int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
@@ -1260,7 +1296,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
notificationBuilder.addAction(android.R.drawable.ic_media_rew,
getString(R.string.rewind_label),
rewindButtonPendingIntent);
- if(UserPreferences.showRewindOnCompactNotification()) {
+ if (UserPreferences.showRewindOnCompactNotification()) {
compactActionList.add(numActions);
}
numActions++;
@@ -1287,7 +1323,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
notificationBuilder.addAction(android.R.drawable.ic_media_ff,
getString(R.string.fast_forward_label),
ffButtonPendingIntent);
- if(UserPreferences.showFastForwardOnCompactNotification()) {
+ if (UserPreferences.showFastForwardOnCompactNotification()) {
compactActionList.add(numActions);
}
numActions++;
@@ -1298,7 +1334,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
notificationBuilder.addAction(android.R.drawable.ic_media_next,
getString(R.string.skip_episode_label),
skipButtonPendingIntent);
- if(UserPreferences.showSkipOnCompactNotification()) {
+ if (UserPreferences.showSkipOnCompactNotification()) {
compactActionList.add(numActions);
}
numActions++;
@@ -1306,7 +1342,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_STOP, numActions);
- notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
+ notificationBuilder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(compactActionList.toArray())
.setShowCancelButton(true)
@@ -1351,9 +1387,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*
* @param fromMediaPlayer if true, the information is gathered from the current Media Player
* and {@param playable} and {@param position} become irrelevant.
- * @param playable the playable for which the current position should be saved, unless
- * {@param fromMediaPlayer} is true.
- * @param position the position that should be saved, unless {@param fromMediaPlayer} is true.
+ * @param playable the playable for which the current position should be saved, unless
+ * {@param fromMediaPlayer} is true.
+ * @param position the position that should be saved, unless {@param fromMediaPlayer} is true.
*/
private synchronized void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
int duration;
@@ -1373,16 +1409,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
}
- private void stopWidgetUpdater() {
- taskManager.cancelWidgetUpdater();
- sendBroadcast(new Intent(STOP_WIDGET_UPDATE));
- }
-
- private void updateWidget() {
- PlaybackService.this.sendBroadcast(new Intent(
- FORCE_WIDGET_UPDATE));
- }
-
public boolean sleepTimerActive() {
return taskManager.isSleepTimerActive();
}
@@ -1421,7 +1447,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
String status = intent.getStringExtra("media_connection_status");
boolean isConnectedToCar = "media_connected".equals(status);
Log.d(TAG, "Received Auto Connection update: " + status);
- if(!isConnectedToCar) {
+ if (!isConnectedToCar) {
Log.d(TAG, "Car was unplugged during playback.");
pauseIfPauseOnDisconnect();
} else {
@@ -1449,6 +1475,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onReceive(Context context, Intent intent) {
+ if (isInitialStickyBroadcast ()) {
+ // Don't pause playback after we just started, just because the receiver
+ // delivers the current headset state (instead of a change)
+ return;
+ }
+
if (TextUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", -1);
if (state != -1) {
@@ -1470,13 +1502,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private final BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- if (TextUtils.equals(intent.getAction(), BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
- int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
- if (state == BluetoothA2dp.STATE_CONNECTED) {
- Log.d(TAG, "Received bluetooth connection intent");
- unpauseIfPauseOnDisconnect(true);
- }
+ if (TextUtils.equals(intent.getAction(), BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
+ if (state == BluetoothA2dp.STATE_CONNECTED) {
+ Log.d(TAG, "Received bluetooth connection intent");
+ unpauseIfPauseOnDisconnect(true);
}
}
}
@@ -1513,10 +1543,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
transientPause = false;
if (!bluetooth && UserPreferences.isUnpauseOnHeadsetReconnect()) {
mediaPlayer.resume();
- } else if (bluetooth && UserPreferences.isUnpauseOnBluetoothReconnect()){
+ } else if (bluetooth && UserPreferences.isUnpauseOnBluetoothReconnect()) {
// let the user know we've started playback again...
Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
- if(v != null) {
+ if (v != null) {
v.vibrate(500);
}
mediaPlayer.resume();
@@ -1529,7 +1559,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- stopSelf();
+ stopService();
}
}
@@ -1597,7 +1627,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return mediaPlayer.getPlayerStatus();
}
- public Playable getPlayable() { return mediaPlayer.getPlayable(); }
+ public Playable getPlayable() {
+ return mediaPlayer.getPlayable();
+ }
public boolean canSetSpeed() {
return mediaPlayer.canSetSpeed();
@@ -1637,7 +1669,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
- public void seekDelta(final int d) {
+ private void seekDelta(final int d) {
mediaPlayer.seekDelta(d);
}
@@ -1653,6 +1685,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* an invalid state.
*/
public int getDuration() {
+ if (mediaPlayer == null) {
+ return INVALID_TIME;
+ }
return mediaPlayer.getDuration();
}
@@ -1661,6 +1696,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* is in an invalid state.
*/
public int getCurrentPosition() {
+ if (mediaPlayer == null) {
+ return INVALID_TIME;
+ }
return mediaPlayer.getPosition();
}
@@ -1692,19 +1730,19 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onPlayFromMediaId(String mediaId, Bundle extras) {
Log.d(TAG, "onPlayFromMediaId: mediaId: " + mediaId + " extras: " + extras.toString());
FeedMedia p = DBReader.getFeedMedia(Long.parseLong(mediaId));
- if(p != null) {
+ if (p != null) {
mediaPlayer.playMediaObject(p, !p.localFileAvailable(), true, true);
}
}
@Override
- public void onPlayFromSearch (String query, Bundle extras) {
+ public void onPlayFromSearch(String query, Bundle extras) {
Log.d(TAG, "onPlayFromSearch query=" + query + " extras=" + extras.toString());
- List<SearchResult> results = FeedSearcher.performSearch(getBaseContext(),query,0);
- for( SearchResult result : results) {
+ List<SearchResult> results = FeedSearcher.performSearch(getBaseContext(), query, 0);
+ for (SearchResult result : results) {
try {
- FeedMedia p = ((FeedItem)(result.getComponent())).getMedia();
+ FeedMedia p = ((FeedItem) (result.getComponent())).getMedia();
mediaPlayer.playMediaObject(p, !p.localFileAvailable(), true, true);
return;
} catch (Exception e) {
@@ -1714,7 +1752,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
}
onPlay();
- return;
}
@Override
@@ -1752,7 +1789,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onSkipToNext() {
Log.d(TAG, "onSkipToNext()");
- if(UserPreferences.shouldHardwareButtonSkip()) {
+ if (UserPreferences.shouldHardwareButtonSkip()) {
mediaPlayer.skip();
} else {
seekDelta(UserPreferences.getFastForwardSecs() * 1000);
@@ -1770,11 +1807,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public boolean onMediaButtonEvent(final Intent mediaButton) {
Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
if (mediaButton != null) {
- KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ KeyEvent keyEvent = (KeyEvent) mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent != null &&
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
- keyEvent.getRepeatCount() == 0){
- handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource());
+ keyEvent.getRepeatCount() == 0) {
+ return handleKeycode(keyEvent.getKeyCode(), false);
}
}
return false;
@@ -1791,29 +1828,38 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
};
- private SharedPreferences.OnSharedPreferenceChangeListener prefListener =
+ private final SharedPreferences.OnSharedPreferenceChangeListener prefListener =
(sharedPreferences, key) -> {
if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) {
updateMediaSessionMetadata(getPlayable());
} else {
flavorHelper.onSharedPreference(key);
}
- };
+ };
interface FlavorHelperCallback {
PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback();
+
void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer);
+
PlaybackServiceMediaPlayer getMediaPlayer();
+
void setIsCasting(boolean isCasting);
+
void sendNotificationBroadcast(int type, int code);
+
void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position);
+
void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
+
MediaSessionCompat getMediaSession();
+
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
+
void unregisterReceiver(BroadcastReceiver receiver);
}
- private FlavorHelperCallback flavorHelperCallback = new FlavorHelperCallback() {
+ private final FlavorHelperCallback flavorHelperCallback = new FlavorHelperCallback() {
@Override
public PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback() {
return PlaybackService.this.mediaPlayerCallback;
@@ -1856,7 +1902,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
UserPreferences.isPersistNotify()) &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
PlaybackService.this.setupNotification(info);
- } else if (!UserPreferences.isPersistNotify()){
+ } else if (!UserPreferences.isPersistNotify()) {
PlaybackService.this.stopForeground(true);
}
}
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 393019fd1..a2481b801 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -24,14 +24,14 @@ import de.danoeh.antennapod.core.util.playback.Playable;
* and remote (cast devices) playback.
*/
public abstract class PlaybackServiceMediaPlayer {
- public static final String TAG = "PlaybackSvcMediaPlayer";
+ private static final String TAG = "PlaybackSvcMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
*/
static final int INVALID_TIME = -1;
- volatile PlayerStatus oldPlayerStatus;
+ private volatile PlayerStatus oldPlayerStatus;
volatile PlayerStatus playerStatus;
/**
@@ -39,8 +39,8 @@ public abstract class PlaybackServiceMediaPlayer {
*/
private WifiManager.WifiLock wifiLock;
- protected final PSMPCallback callback;
- protected final Context context;
+ final PSMPCallback callback;
+ final Context context;
PlaybackServiceMediaPlayer(@NonNull Context context,
@NonNull PSMPCallback callback){
@@ -279,7 +279,7 @@ public abstract class PlaybackServiceMediaPlayer {
final synchronized void acquireWifiLockIfNecessary() {
if (shouldLockWifi()) {
if (wifiLock == null) {
- wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
+ wifiLock = ((WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
wifiLock.setReferenceCounted(false);
}
@@ -365,7 +365,7 @@ public abstract class PlaybackServiceMediaPlayer {
* Holds information about a PSMP object.
*/
public static class PSMPInfo {
- public PlayerStatus oldPlayerStatus;
+ public final PlayerStatus oldPlayerStatus;
public PlayerStatus playerStatus;
public Playable playable;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index 0c7d5e718..3d97e862a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -295,7 +295,7 @@ public class PlaybackServiceTaskManager {
/**
* Sleeps for a given time and then pauses playback.
*/
- protected class SleepTimer implements Runnable {
+ class SleepTimer implements Runnable {
private static final String TAG = "SleepTimer";
private static final long UPDATE_INTERVAL = 1000L;
private static final long NOTIFICATION_THRESHOLD = 10000;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
index fcd96826b..c0b1b3bc0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
@@ -7,14 +7,14 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
-public class ShakeListener implements SensorEventListener
+class ShakeListener implements SensorEventListener
{
private static final String TAG = ShakeListener.class.getSimpleName();
private Sensor mAccelerometer;
private SensorManager mSensorMgr;
- private PlaybackServiceTaskManager.SleepTimer mSleepTimer;
- private Context mContext;
+ private final PlaybackServiceTaskManager.SleepTimer mSleepTimer;
+ private final Context mContext;
public ShakeListener(Context context, PlaybackServiceTaskManager.SleepTimer sleepTimer) {
mContext = context;
@@ -22,7 +22,7 @@ public class ShakeListener implements SensorEventListener
resume();
}
- public void resume() {
+ private void resume() {
// only a precaution, the user should actually not be able to activate shake to reset
// when the accelerometer is not available
mSensorMgr = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
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 cb268daca..456d05ded 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.storage;
import android.database.Cursor;
import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -13,13 +14,9 @@ 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;
-import de.danoeh.antennapod.core.feed.ID3Chapter;
-import de.danoeh.antennapod.core.feed.SimpleChapter;
-import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
@@ -47,7 +44,7 @@ public final class DBReader {
/**
* Maximum size of the list returned by {@link #getDownloadLog()}.
*/
- public static final int DOWNLOAD_LOG_SIZE = 200;
+ private static final int DOWNLOAD_LOG_SIZE = 200;
private DBReader() {
@@ -123,7 +120,7 @@ public final class DBReader {
loadFeedDataOfFeedItemList(items);
}
- public static void loadTagsOfFeedItemList(List<FeedItem> items) {
+ private static void loadTagsOfFeedItemList(List<FeedItem> items) {
LongList favoriteIds = getFavoriteIDList();
LongList queueIds = getQueueIDList();
@@ -144,7 +141,7 @@ public final class DBReader {
*
* @param items The FeedItems whose Feed-objects should be loaded.
*/
- public static void loadFeedDataOfFeedItemList(List<FeedItem> items) {
+ private static void loadFeedDataOfFeedItemList(List<FeedItem> items) {
List<Feed> feeds = getFeedList();
Map<Long, Feed> feedIndex = new ArrayMap<>(feeds.size());
@@ -204,25 +201,15 @@ public final class DBReader {
private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter, Cursor cursor) {
List<FeedItem> result = new ArrayList<>(cursor.getCount());
- LongList imageIds = new LongList(cursor.getCount());
LongList itemIds = new LongList(cursor.getCount());
if (cursor.moveToFirst()) {
do {
- int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
- long imageId = cursor.getLong(indexImage);
- imageIds.add(imageId);
-
FeedItem item = FeedItem.fromCursor(cursor);
result.add(item);
itemIds.add(item.getId());
} while (cursor.moveToNext());
- Map<Long, FeedImage> images = getFeedImages(adapter, imageIds.toArray());
Map<Long, FeedMedia> medias = getFeedMedia(adapter, itemIds);
- for (int i = 0; i < result.size(); i++) {
- FeedItem item = result.get(i);
- long imageId = imageIds.get(i);
- FeedImage image = images.get(imageId);
- item.setImage(image);
+ for (FeedItem item : result) {
FeedMedia media = medias.get(item.getId());
item.setMedia(media);
if (media != null) {
@@ -257,24 +244,9 @@ public final class DBReader {
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, Cursor cursor) {
- final FeedImage image;
- int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
- long imageId = cursor.getLong(indexImage);
- if (imageId != 0) {
- image = getFeedImage(adapter, imageId);
- } else {
- image = null;
- }
-
Feed feed = Feed.fromCursor(cursor);
- if (image != null) {
- feed.setImage(image);
- image.setOwner(feed);
- }
-
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
-
return feed;
}
@@ -415,7 +387,7 @@ public final class DBReader {
}
}
- public static LongList getFavoriteIDList() {
+ private static LongList getFavoriteIDList() {
Log.d(TAG, "getFavoriteIDList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
@@ -666,7 +638,7 @@ public final class DBReader {
}
}
- static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
+ 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;
try {
@@ -717,7 +689,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 = "";
@@ -800,7 +772,7 @@ public final class DBReader {
}
}
- static void loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
+ private static void loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
Cursor cursor = null;
try {
cursor = adapter.getSimpleChaptersOfFeedItemCursor(item);
@@ -842,62 +814,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
@@ -1026,14 +942,14 @@ public final class DBReader {
/**
* Simply sums up time of podcasts that are marked as played
*/
- public long totalTimeCountAll;
+ public final long totalTimeCountAll;
/**
* Respects speed, listening twice, ...
*/
- public long totalTime;
+ public final long totalTime;
- public List<StatisticsItem> feedTime;
+ public final List<StatisticsItem> feedTime;
public StatisticsData(long totalTime, long totalTimeCountAll, List<StatisticsItem> feedTime) {
this.totalTime = totalTime;
@@ -1043,26 +959,26 @@ public final class DBReader {
}
public static class StatisticsItem {
- public Feed feed;
- public long time;
+ public final Feed feed;
+ public final long time;
/**
* Respects speed, listening twice, ...
*/
- public long timePlayed;
+ public final long timePlayed;
/**
* Simply sums up time of podcasts that are marked as played
*/
- public long timePlayedCountAll;
- public long episodes;
+ public final long timePlayedCountAll;
+ public final long episodes;
/**
* Episodes that are actually played
*/
- public long episodesStarted;
+ public final long episodesStarted;
/**
* All episodes that are marked as played (or have position != 0)
*/
- public long episodesStartedIncludingMarked;
+ public final long episodesStartedIncludingMarked;
public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll,
long episodes, long episodesStarted, long episodesStartedIncludingMarked) {
@@ -1198,12 +1114,12 @@ public final class DBReader {
}
public static class NavDrawerData {
- public List<Feed> feeds;
- public int queueSize;
- public int numNewItems;
- public int numDownloadedItems;
- public LongIntMap feedCounters;
- public int reclaimableSpace;
+ public final List<Feed> feeds;
+ public final int queueSize;
+ public final int numNewItems;
+ public final int numDownloadedItems;
+ public final LongIntMap feedCounters;
+ public final int reclaimableSpace;
public NavDrawerData(List<Feed> feeds,
int queueSize,
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 148b530a7..8eed10cd7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -4,6 +4,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.util.ArrayList;
@@ -34,13 +36,14 @@ 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;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import static android.content.Context.MODE_PRIVATE;
-import static android.provider.Contacts.SettingsColumns.KEY;
/**
* Provides methods for doing common tasks that use DBReader and DBWriter.
@@ -48,13 +51,13 @@ import static android.provider.Contacts.SettingsColumns.KEY;
public final class DBTasks {
private static final String TAG = "DBTasks";
- public static final String PREF_NAME = "dbtasks";
+ private static final String PREF_NAME = "dbtasks";
private static final String PREF_LAST_REFRESH = "last_refresh";
/**
* Executor service used by the autodownloadUndownloadedEpisodes method.
*/
- private static ExecutorService autodownloadExec;
+ private static final ExecutorService autodownloadExec;
static {
autodownloadExec = Executors.newSingleThreadExecutor(r -> {
@@ -124,16 +127,13 @@ public final class DBTasks {
media);
}
}
- // Start playback Service
- Intent launchIntent = new Intent(context, PlaybackService.class);
- launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
- startWhenPrepared);
- launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- shouldStream);
- launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
- true);
- context.startService(launchIntent);
+
+ new PlaybackServiceStarter(context, media)
+ .callEvenIfRunning(true)
+ .startWhenPrepared(startWhenPrepared)
+ .shouldStream(shouldStream)
+ .start();
+
if (showPlayer) {
// Launch media player
context.startActivity(PlaybackService.getPlayerActivityIntent(
@@ -143,55 +143,70 @@ 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);
}
}
- private static AtomicBoolean isRefreshing = new AtomicBoolean(false);
+ private static final AtomicBoolean isRefreshing = new AtomicBoolean(false);
/**
* Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
* enqueuing Feeds for download from a previous call
*
- * @param context Might be used for accessing the database
- * @param feeds List of Feeds that should be refreshed.
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
*/
- public static void refreshAllFeeds(final Context context,
- final List<Feed> feeds) {
- if (isRefreshing.compareAndSet(false, true)) {
- new Thread() {
- public void run() {
- if (feeds != null) {
- refreshFeeds(context, feeds);
- } else {
- refreshFeeds(context, DBReader.getFeedList());
- }
- isRefreshing.set(false);
+ public static void refreshAllFeeds(final Context context, final List<Feed> feeds) {
+ refreshAllFeeds(context, feeds, null);
+ }
+
+ /**
+ * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
+ * enqueuing Feeds for download from a previous call
+ *
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
+ * @param callback Called after everything was added enqueued for download. Might be null.
+ */
+ public static void refreshAllFeeds(final Context context, final List<Feed> feeds, @Nullable Runnable callback) {
+ if (!isRefreshing.compareAndSet(false, true)) {
+ Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
+ return;
+ }
- SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
- prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
+ new Thread(() -> {
+ if (feeds != null) {
+ refreshFeeds(context, feeds);
+ } else {
+ refreshFeeds(context, DBReader.getFeedList());
+ }
+ isRefreshing.set(false);
- if (FlattrUtils.hasToken()) {
- Log.d(TAG, "Flattring all pending things.");
- new FlattrClickWorker(context).executeAsync(); // flattr pending things
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
- Log.d(TAG, "Fetching flattr status.");
- new FlattrStatusFetcher(context).start();
+ if (FlattrUtils.hasToken()) {
+ Log.d(TAG, "Flattring all pending things.");
+ new FlattrClickWorker(context).executeAsync(); // flattr pending things
- }
- if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
- GpodnetSyncService.sendSyncIntent(context);
- }
- Log.d(TAG, "refreshAllFeeds autodownload");
- autodownloadUndownloadedItems(context);
- }
- }.start();
- } else {
- Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
- }
+ Log.d(TAG, "Fetching flattr status.");
+ new FlattrStatusFetcher(context).start();
+
+ }
+ if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
+ GpodnetSyncService.sendSyncIntent(context);
+ }
+ // 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();
+ }
+ }).start();
}
/**
@@ -224,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.
@@ -293,7 +287,7 @@ public final class DBTasks {
* @param context Used for requesting the download.
* @param feed The Feed object.
*/
- public static void refreshFeed(Context context, Feed feed)
+ private static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
Log.d(TAG, "refreshFeed(feed.id: " + feed.getId() +")");
refreshFeed(context, feed, false, false);
@@ -365,27 +359,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.
@@ -804,7 +777,7 @@ public final class DBTasks {
*/
abstract static class QueryTask<T> implements Callable<T> {
private T result;
- private Context context;
+ private final Context context;
public QueryTask(Context context) {
this.context = context;
@@ -821,7 +794,7 @@ public final class DBTasks {
public abstract void execute(PodDBAdapter adapter);
- protected void setResult(T result) {
+ void setResult(T result) {
this.result = result;
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
new file mode 100644
index 000000000..29ed5f7f9
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java
@@ -0,0 +1,292 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.MediaMetadataRetriever;
+import android.util.Log;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
+class DBUpgrader {
+ /**
+ * Upgrades the given database to a new schema version
+ */
+ static void upgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ if (oldVersion <= 1) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + PodDBAdapter.KEY_TYPE + " TEXT");
+ }
+ if (oldVersion <= 2) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + PodDBAdapter.KEY_LINK + " TEXT");
+ }
+ if (oldVersion <= 3) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_ITEM_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 4) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ + PodDBAdapter.KEY_FEED_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 5) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + PodDBAdapter.KEY_REASON_DETAILED + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE + " TEXT");
+ }
+ if (oldVersion <= 6) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + PodDBAdapter.KEY_CHAPTER_TYPE + " INTEGER");
+ }
+ if (oldVersion <= 7) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE
+ + " INTEGER");
+ }
+ if (oldVersion <= 8) {
+ final int KEY_ID_POSITION = 0;
+ final int KEY_MEDIA_POSITION = 1;
+
+ // Add feeditem column to feedmedia table
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_FEEDITEM
+ + " INTEGER");
+ Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS,
+ new String[]{PodDBAdapter.KEY_ID, PodDBAdapter.KEY_MEDIA}, "? > 0",
+ new String[]{PodDBAdapter.KEY_MEDIA}, null, null, null);
+ if (feeditemCursor.moveToFirst()) {
+ db.beginTransaction();
+ ContentValues contentValues = new ContentValues();
+ do {
+ long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
+ contentValues.put(PodDBAdapter.KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
+ db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, PodDBAdapter.KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ contentValues.clear();
+ } while (feeditemCursor.moveToNext());
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+ feeditemCursor.close();
+ }
+ if (oldVersion <= 9) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ + " INTEGER DEFAULT 1");
+ }
+ if (oldVersion <= 10) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ + " INTEGER");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION
+ + " INTEGER");
+ }
+ if (oldVersion <= 11) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_USERNAME
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_PASSWORD
+ + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE
+ + " INTEGER");
+ }
+ if (oldVersion <= 12) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_NEXT_PAGE_LINK + " TEXT");
+ }
+ if (oldVersion <= 13) {
+ // remove duplicate rows in "Chapters" table that were created because of a bug.
+ db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
+ "(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.KEY_ID,
+ PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
+ PodDBAdapter.KEY_TITLE,
+ PodDBAdapter.KEY_START,
+ PodDBAdapter.KEY_FEEDITEM,
+ PodDBAdapter.KEY_LINK,
+ PodDBAdapter.KEY_CHAPTER_TYPE));
+ }
+ if (oldVersion <= 14) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = "
+ + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
+
+ // create indexes
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+ }
+ if (oldVersion <= 15) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=0");
+ Cursor c = db.rawQuery("SELECT " + PodDBAdapter.KEY_FILE_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=1 "
+ + " AND " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
+ if (c.moveToFirst()) {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ do {
+ String fileUrl = c.getString(0);
+ try {
+ mmr.setDataSource(fileUrl);
+ byte[] image = mmr.getEmbeddedPicture();
+ if (image != null) {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=1"
+ + " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
+ } else {
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ + " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } while (c.moveToNext());
+ }
+ c.close();
+ }
+ if (oldVersion <= 16) {
+ String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_FEEDITEM
+ + " WHERE "
+ + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ + " = 0 AND " // unplayed
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION + " = 0 AND " // not partially played
+ + PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_ID + " IS NULL"; // not in queue
+ String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + PodDBAdapter.KEY_READ + "=" + FeedItem.NEW
+ + " WHERE " + PodDBAdapter.KEY_ID + " IN (" + selectNew + ")";
+ Log.d("Migration", "SQL: " + sql);
+ db.execSQL(sql);
+ }
+ if (oldVersion <= 17) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
+ }
+ if (oldVersion < 1030005) {
+ db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
+ "(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
+ "AND id NOT IN (SELECT feeditem FROM Queue)");
+ }
+ if (oldVersion < 1040001) {
+ db.execSQL(PodDBAdapter.CREATE_TABLE_FAVORITES);
+ }
+ if (oldVersion < 1040002) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
+ }
+ if (oldVersion < 1040013) {
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
+ }
+ if (oldVersion < 1050003) {
+ // Migrates feed list filter data
+
+ db.beginTransaction();
+
+ // Change to intermediate values to avoid overwriting in the following find/replace
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'unplayed', 'noplay')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_queued', 'noqueue')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_downloaded', 'nodl')");
+
+ // Replace played, queued, and downloaded with their opposites
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'played', 'unplayed')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'queued', 'not_queued')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'downloaded', 'not_downloaded')");
+
+ // Now replace intermediates for unplayed, not queued, etc. with their opposites
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noplay', 'played')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noqueue', 'queued')");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'nodl', 'downloaded')");
+
+ // Paused doesn't have an opposite, so unplayed is the next best option
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
+ "SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'paused', 'unplayed')");
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ // and now get ready for autodownload filters
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
+
+ // and now auto refresh
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
+ }
+ if (oldVersion < 1050004) {
+ // prevent old timestamps to be misinterpreted as ETags
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " SET " + PodDBAdapter.KEY_LASTUPDATE + "=NULL");
+ }
+ if (oldVersion < 1060200) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_CUSTOM_TITLE + " TEXT");
+ }
+ if (oldVersion < 1060596) {
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
+
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ + " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_IMAGE + ")");
+
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ + " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_IMAGE + ")");
+
+ db.execSQL("DROP TABLE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES);
+ }
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index 49ec07004..8bb5bc31a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -7,8 +7,7 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
-import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.event.MessageEvent;
+import de.danoeh.antennapod.core.util.IntentUtils;
import org.shredzone.flattr4j.model.Flattr;
import java.io.File;
@@ -25,14 +24,15 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
+import de.danoeh.antennapod.core.event.MessageEvent;
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;
@@ -43,6 +43,7 @@ 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.LongList;
+import de.danoeh.antennapod.core.util.Permutor;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing;
@@ -115,11 +116,8 @@ public class DBWriter {
true);
editor.commit();
}
- if (PlaybackPreferences
- .getCurrentlyPlayingFeedMediaId() == media
- .getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == media.getId()) {
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
}
}
// Gpodder: queue delete action for synchronization
@@ -156,8 +154,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,
@@ -165,17 +162,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<>();
@@ -187,6 +173,9 @@ public class DBWriter {
if(queue.remove(item)) {
removed.add(item);
}
+ if (item.getState() == FeedItem.State.PLAYING && PlaybackService.isRunning) {
+ context.stopService(new Intent(context, PlaybackService.class));
+ }
if (item.getMedia() != null
&& item.getMedia().isDownloaded()) {
File mediaFile = new File(item.getMedia()
@@ -196,16 +185,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();
@@ -382,8 +361,8 @@ public class DBWriter {
// add item to either front ot back of queue
boolean addToFront = UserPreferences.enqueueAtFront();
if (addToFront) {
- queue.add(0 + i, item);
- events.add(QueueEvent.added(item, 0 + i));
+ queue.add(i, item);
+ events.add(QueueEvent.added(item, i));
} else {
queue.add(item);
events.add(QueueEvent.added(item, queue.size() - 1));
@@ -478,22 +457,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();
@@ -782,21 +745,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) {
@@ -838,9 +786,9 @@ public class DBWriter {
*
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
*/
- public static Future<?> setFeedItemFlattrStatus(final Context context,
- final FeedItem item,
- final boolean startFlattrClickWorker) {
+ private static Future<?> setFeedItemFlattrStatus(final Context context,
+ final FeedItem item,
+ final boolean startFlattrClickWorker) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -992,6 +940,32 @@ public class DBWriter {
}
/**
+ * Similar to sortQueue, but allows more complex reordering by providing whole-queue context.
+ * @param permutor Encapsulates whole-Queue reordering logic.
+ * @param broadcastUpdate <code>true</code> if this operation should trigger a
+ * QueueUpdateBroadcast. This option should be set to <code>false</code>
+ * if the caller wants to avoid unexpected updates of the GUI.
+ */
+ public static Future<?> reorderQueue(final Permutor<FeedItem> permutor, final boolean broadcastUpdate) {
+ return dbExec.submit(() -> {
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+
+ if (queue != null) {
+ permutor.reorder(queue);
+ adapter.setQueue(queue);
+ if (broadcastUpdate) {
+ EventBus.getDefault().post(QueueEvent.sorted(queue));
+ }
+ } else {
+ Log.e(TAG, "reorderQueue: Could not load queue");
+ }
+ adapter.close();
+ });
+ }
+
+ /**
* Sets the 'auto_download'-attribute of specific FeedItem.
*
* @param feedItem FeedItem.
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 7051d7f4d..827874f54 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
@@ -4,10 +4,13 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.util.IntentUtils;
import org.apache.commons.io.FilenameUtils;
import java.io.File;
@@ -33,8 +36,8 @@ public class DownloadRequester {
private static final String TAG = "DownloadRequester";
public static final String IMAGE_DOWNLOADPATH = "images/";
- public static final String FEED_DOWNLOADPATH = "cache/";
- public static final String MEDIA_DOWNLOADPATH = "media/";
+ private static final String FEED_DOWNLOADPATH = "cache/";
+ private static final String MEDIA_DOWNLOADPATH = "media/";
/**
* Denotes the page of the feed that is contained in the DownloadRequest sent by the DownloadRequester.
@@ -48,7 +51,7 @@ public class DownloadRequester {
private static DownloadRequester downloader;
- private Map<String, DownloadRequest> downloads;
+ private final Map<String, DownloadRequest> downloads;
private DownloadRequester() {
downloads = new ConcurrentHashMap<>();
@@ -81,7 +84,7 @@ public class DownloadRequester {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
- context.startService(launchIntent);
+ ContextCompat.startForegroundService(context, launchIntent);
return true;
}
@@ -89,7 +92,9 @@ public class DownloadRequester {
private void download(Context context, FeedFile item, FeedFile container, File dest,
boolean overwriteIfExists, String username, String password,
String lastModified, boolean deleteOnFailure, Bundle arguments) {
- final boolean partiallyDownloadedFileExists = item.getFile_url() != null;
+ final boolean partiallyDownloadedFileExists = item.getFile_url() != null && new File(item.getFile_url()).exists();
+
+ Log.d(TAG, "partiallyDownloadedFileExists: " + partiallyDownloadedFileExists);
if (isDownloadingFile(item)) {
Log.e(TAG, "URL " + item.getDownload_url()
+ " is already being downloaded");
@@ -174,8 +179,8 @@ public class DownloadRequester {
args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
args.putBoolean(REQUEST_ARG_LOAD_ALL_PAGES, loadAllPages);
- download(context, feed, null, new File(getFeedfilePath(context),
- getFeedfileName(feed)), true, username, password, lastModified, true, args);
+ download(context, feed, null, new File(getFeedfilePath(), getFeedfileName(feed)),
+ true, username, password, lastModified, true, args);
}
}
@@ -201,8 +206,7 @@ public class DownloadRequester {
if (feedmedia.getFile_url() != null) {
dest = new File(feedmedia.getFile_url());
} else {
- dest = new File(getMediafilePath(context, feedmedia),
- getMediafilename(feedmedia));
+ dest = new File(getMediafilePath(feedmedia), getMediafilename(feedmedia));
}
download(context, feedmedia, feed,
dest, false, username, password, null, false, null);
@@ -240,6 +244,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);
}
@@ -248,8 +253,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);
}
/**
@@ -303,13 +307,11 @@ public class DownloadRequester {
return downloads.size();
}
- public synchronized String getFeedfilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
- .toString() + "/";
+ private synchronized String getFeedfilePath() throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(FEED_DOWNLOADPATH).toString() + "/";
}
- public synchronized String getFeedfileName(Feed feed) {
+ private synchronized String getFeedfileName(Feed feed) {
String filename = feed.getDownload_url();
if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
filename = feed.getTitle();
@@ -317,10 +319,8 @@ public class DownloadRequester {
return "feed-" + FileNameGenerator.generateFileName(filename);
}
- public synchronized String getMediafilePath(Context context, FeedMedia media)
- throws DownloadRequestException {
+ private synchronized String getMediafilePath(FeedMedia media) throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
- context,
MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(media.getItem()
.getFeed().getTitle()) + "/"
@@ -328,8 +328,7 @@ public class DownloadRequester {
return externalStorage.toString();
}
- private File getExternalFilesDirOrThrowException(Context context,
- String type) throws DownloadRequestException {
+ private File getExternalFilesDirOrThrowException(String type) throws DownloadRequestException {
File result = UserPreferences.getDataFolder(type);
if (result == null) {
throw new DownloadRequestException(
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
index 97cbdca33..aae5b352e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
@@ -15,7 +15,7 @@ public abstract class EpisodeCleanupAlgorithm {
* or getPerformCleanupParameter.
* @return The number of episodes that were deleted.
*/
- public abstract int performCleanup(Context context, int numToRemove);
+ protected abstract int performCleanup(Context context, int numToRemove);
public int performCleanup(Context context) {
return performCleanup(context, getDefaultCleanupParameter());
@@ -26,7 +26,7 @@ public abstract class EpisodeCleanupAlgorithm {
* space to free to satisfy the episode cache conditions. If the conditions are already satisfied, this
* method should not have any effects.
*/
- public abstract int getDefaultCleanupParameter();
+ protected abstract int getDefaultCleanupParameter();
/**
* Cleans up just enough episodes to make room for the requested number
@@ -48,7 +48,7 @@ public abstract class EpisodeCleanupAlgorithm {
* @param amountOfRoomNeeded the number of episodes we want to download
* @return the number of episodes to delete in order to make room
*/
- protected int getNumEpisodesToCleanup(final int amountOfRoomNeeded) {
+ int getNumEpisodesToCleanup(final int amountOfRoomNeeded) {
if (amountOfRoomNeeded >= 0
&& UserPreferences.getEpisodeCacheSize() != UserPreferences
.getEpisodeCacheSizeUnlimited()) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
index 09949b87e..d84279f6e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedItemStatistics.java
@@ -8,11 +8,11 @@ import java.util.Date;
* Contains information about a feed's items.
*/
public class FeedItemStatistics {
- private long feedID;
- private int numberOfItems;
- private int numberOfNewItems;
- private int numberOfInProgressItems;
- private Date lastUpdate;
+ private final long feedID;
+ private final int numberOfItems;
+ private final int numberOfNewItems;
+ private final int numberOfInProgressItems;
+ private final Date lastUpdate;
private static final Date UNKNOWN_DATE = new Date(0);
@@ -26,7 +26,7 @@ public class FeedItemStatistics {
* @param lastUpdate pubDate of the latest episode. A lastUpdate value of 0 will be interpreted as DATE_UNKOWN if
* numberOfItems is 0.
*/
- public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) {
+ private FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) {
this.feedID = feedID;
this.numberOfItems = numberOfItems;
this.numberOfNewItems = numberOfNewItems;
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 dc8692866..51b41d3b3 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
@@ -3,31 +3,21 @@ package de.danoeh.antennapod.core.storage;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.DatabaseErrorHandler;
import android.database.DatabaseUtils;
+import android.database.DefaultDatabaseErrorHandler;
import android.database.MergeCursor;
import android.database.SQLException;
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.text.TextUtils;
import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
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,6 +26,14 @@ 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
// TODO Remove media column from feeditem table
@@ -45,7 +43,7 @@ import de.greenrobot.event.EventBus;
public class PodDBAdapter {
private static final String TAG = "PodDBAdapter";
- private static final String DATABASE_NAME = "Antennapod.db";
+ public static final String DATABASE_NAME = "Antennapod.db";
/**
* Maximum number of arguments for IN-operator.
@@ -73,6 +71,7 @@ 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";
public static final String KEY_MEDIA = "media";
public static final String KEY_DOWNLOADED = "downloaded";
@@ -113,26 +112,26 @@ 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
+ " INTEGER PRIMARY KEY AUTOINCREMENT ,";
- public static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
+ private static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
+ TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_CUSTOM_TITLE + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ 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,"
@@ -146,7 +145,7 @@ public class PodDBAdapter {
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0)";
- public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE
+ " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
@@ -154,15 +153,10 @@ 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)";
- public 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)";
-
- public static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
+ 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
+ " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
@@ -173,53 +167,48 @@ public class PodDBAdapter {
+ KEY_HAS_EMBEDDED_PICTURE + " INTEGER,"
+ KEY_LAST_PLAYED_TIME + " INTEGER)";
- public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
+ " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
+ " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
+ " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
+ KEY_DOWNLOADSTATUS_TITLE + " TEXT)";
- public static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
+ private static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
+ TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
- public static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
+ private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
+ TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
// SQL Statements for creating indexes
- public static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")";
- public static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
- + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
- + KEY_IMAGE + ")";
-
- public static final String CREATE_INDEX_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 + ")";
- public 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 + ")";
-
- public 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 + ")";
- public 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 + ")";
- public 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 + ")";
- public 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)";
@@ -239,7 +228,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE,
TABLE_NAME_FEEDS + "." + KEY_LANGUAGE,
TABLE_NAME_FEEDS + "." + KEY_AUTHOR,
- TABLE_NAME_FEEDS + "." + KEY_IMAGE,
+ TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEEDS + "." + KEY_TYPE,
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
@@ -272,7 +261,7 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
- TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
};
@@ -282,7 +271,6 @@ public class PodDBAdapter {
private static final String[] ALL_TABLES = {
TABLE_NAME_FEEDS,
TABLE_NAME_FEED_ITEMS,
- TABLE_NAME_FEED_IMAGES,
TABLE_NAME_FEED_MEDIA,
TABLE_NAME_DOWNLOAD_LOG,
TABLE_NAME_QUEUE,
@@ -307,72 +295,57 @@ public class PodDBAdapter {
KEY_CONTENT_ENCODED, KEY_FEED};
private static Context context;
- private static PodDBHelper dbHelper;
private static volatile SQLiteDatabase db;
- private static Lock dbLock = new ReentrantLock();
- private static AtomicInteger counter = new AtomicInteger(0);
+ private static int counter = 0;
public static void init(Context context) {
PodDBAdapter.context = context.getApplicationContext();
}
- private static class PodDBHelperholder {
- public static final PodDBHelper dbHelper = new PodDBHelper(PodDBAdapter.context, DATABASE_NAME, null);
+ // Bill Pugh Singleton Implementation
+ private static class SingletonHolder {
+ private static final PodDBHelper dbHelper = new PodDBHelper(PodDBAdapter.context, DATABASE_NAME, null);
+ private static final PodDBAdapter dbAdapter = new PodDBAdapter();
}
public static PodDBAdapter getInstance() {
- dbHelper = PodDBHelperholder.dbHelper;
- return new PodDBAdapter();
+ return SingletonHolder.dbAdapter;
}
private PodDBAdapter() {
}
- public PodDBAdapter open() {
- int adapters = counter.incrementAndGet();
- Log.v(TAG, "Opening DB #" + adapters);
+ public synchronized PodDBAdapter open() {
+ counter++;
+ Log.v(TAG, "Opening DB #" + counter);
- if ((db == null) || (!db.isOpen()) || (db.isReadOnly())) {
- try {
- dbLock.lock();
- if ((db == null) || (!db.isOpen()) || (db.isReadOnly())) {
- db = openDb();
- }
- } finally {
- dbLock.unlock();
- }
+ if (db == null || !db.isOpen() || db.isReadOnly()) {
+ db = openDb();
}
return this;
}
private SQLiteDatabase openDb() {
- SQLiteDatabase newDb = null;
+ SQLiteDatabase newDb;
try {
- newDb = dbHelper.getWritableDatabase();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- newDb.enableWriteAheadLogging();
- }
+ newDb = SingletonHolder.dbHelper.getWritableDatabase();
+ newDb.enableWriteAheadLogging();
} catch (SQLException ex) {
Log.e(TAG, Log.getStackTraceString(ex));
- newDb = dbHelper.getReadableDatabase();
+ newDb = SingletonHolder.dbHelper.getReadableDatabase();
}
return newDb;
}
- public void close() {
- int adapters = counter.decrementAndGet();
- Log.v(TAG, "Closing DB #" + adapters);
+ public synchronized void close() {
+ counter--;
+ Log.v(TAG, "Closing DB #" + counter);
- if (adapters == 0) {
+ if (counter == 0) {
Log.v(TAG, "Closing DB, really");
- try {
- dbLock.lock();
- db.close();
- db = null;
- } finally {
- dbLock.unlock();
- }
+ db.close();
+ db = null;
}
}
@@ -394,7 +367,7 @@ public class PodDBAdapter {
*
* @return the id of the entry
*/
- public long setFeed(Feed feed) {
+ private long setFeed(Feed feed) {
ContentValues values = new ContentValues();
values.put(KEY_TITLE, feed.getFeedTitle());
values.put(KEY_LINK, feed.getLink());
@@ -402,12 +375,7 @@ public class PodDBAdapter {
values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
values.put(KEY_AUTHOR, feed.getAuthor());
values.put(KEY_LANGUAGE, feed.getLanguage());
- if (feed.getImage() != null) {
- if (feed.getImage().getId() == 0) {
- setImage(feed.getImage());
- }
- values.put(KEY_IMAGE, feed.getImage().getId());
- }
+ values.put(KEY_IMAGE_URL, feed.getImageUrl());
values.put(KEY_FILE_URL, feed.getFile_url());
values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
@@ -464,58 +432,7 @@ public class PodDBAdapter {
}
/**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- */
- public long setImage(FeedImage image) {
- boolean startedTransaction = false;
-
- try {
- if (!db.inTransaction()) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
- 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
*/
@@ -580,11 +497,7 @@ public class PodDBAdapter {
*/
public void setCompleteFeed(Feed... feeds) {
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
for (Feed feed : feeds) {
setFeed(feed);
if (feed.getItems() != null) {
@@ -630,31 +543,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) {
@@ -665,11 +553,7 @@ public class PodDBAdapter {
public void setFeedItemlist(List<FeedItem> items) {
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
for (FeedItem item : items) {
setFeedItem(item, true);
}
@@ -684,11 +568,7 @@ public class PodDBAdapter {
public long setSingleFeedItem(FeedItem item) {
long result = 0;
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
result = setFeedItem(item, true);
db.setTransactionSuccessful();
} catch (SQLException e) {
@@ -789,12 +669,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));
@@ -814,11 +689,7 @@ public class PodDBAdapter {
public void setFeedItemRead(int played, long itemId, long mediaId,
boolean resetMediaPosition) {
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
ContentValues values = new ContentValues();
values.put(KEY_READ, played);
@@ -846,11 +717,7 @@ public class PodDBAdapter {
*/
public void setFeedItemRead(int read, long... itemIds) {
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
ContentValues values = new ContentValues();
for (long id : itemIds) {
values.clear();
@@ -865,7 +732,7 @@ public class PodDBAdapter {
}
}
- public void setChapters(FeedItem item) {
+ private void setChapters(FeedItem item) {
ContentValues values = new ContentValues();
for (Chapter chapter : item.getChapters()) {
values.put(KEY_TITLE, chapter.getTitle());
@@ -933,11 +800,7 @@ public class PodDBAdapter {
public void setFavorites(List<FeedItem> favorites) {
ContentValues values = new ContentValues();
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
db.delete(TABLE_NAME_FAVORITES, null, null);
for (int i = 0; i < favorites.size(); i++) {
FeedItem item = favorites.get(i);
@@ -977,7 +840,7 @@ public class PodDBAdapter {
db.execSQL(deleteClause);
}
- public boolean isItemInFavorites(FeedItem item) {
+ private boolean isItemInFavorites(FeedItem item) {
String query = String.format("SELECT %s from %s WHERE %s=%d",
KEY_ID, TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.getId());
Cursor c = db.rawQuery(query, null);
@@ -986,25 +849,10 @@ 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 {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
+ db.beginTransactionNonExclusive();
db.delete(TABLE_NAME_QUEUE, null, null);
for (int i = 0; i < queue.size(); i++) {
FeedItem item = queue.get(i);
@@ -1025,7 +873,7 @@ public class PodDBAdapter {
db.delete(TABLE_NAME_QUEUE, null, null);
}
- public void removeFeedMedia(FeedMedia media) {
+ private void removeFeedMedia(FeedMedia media) {
// delete download log entries for feed media
db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_FEEDFILE + "=? AND " + KEY_FEEDFILETYPE + "=?",
new String[]{String.valueOf(media.getId()), String.valueOf(FeedMedia.FEEDFILETYPE_FEEDMEDIA)});
@@ -1034,29 +882,21 @@ public class PodDBAdapter {
new String[]{String.valueOf(media.getId())});
}
- public void removeChaptersOfItem(FeedItem item) {
+ private void removeChaptersOfItem(FeedItem item) {
db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?",
new String[]{String.valueOf(item.getId())});
}
- public 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.
*/
- public void removeFeedItem(FeedItem item) {
+ private void removeFeedItem(FeedItem item) {
if (item.getMedia() != null) {
removeFeedMedia(item.getMedia());
}
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())});
}
@@ -1066,14 +906,7 @@ public class PodDBAdapter {
*/
public void removeFeed(Feed feed) {
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- db.beginTransactionNonExclusive();
- } else {
- db.beginTransaction();
- }
- if (feed.getImage() != null) {
- removeFeedImage(feed.getImage());
- }
+ db.beginTransactionNonExclusive();
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
@@ -1127,7 +960,7 @@ public class PodDBAdapter {
return getAllItemsOfFeedCursor(feed.getId());
}
- public final Cursor getAllItemsOfFeedCursor(final long feedId) {
+ private Cursor getAllItemsOfFeedCursor(final long feedId) {
return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FEED
+ "=?", new String[]{String.valueOf(feedId)}, null, null,
null);
@@ -1144,18 +977,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
@@ -1370,11 +1191,7 @@ public class PodDBAdapter {
if (size == 1) {
return "(?)";
}
- StringBuilder builder =
- new StringBuilder("(")
- .append(TextUtils.join(",", Collections.nCopies(size, "?")))
- .append(")");
- return builder.toString();
+ return "(" + TextUtils.join(",", Collections.nCopies(size, "?")) + ")";
}
public final Cursor getFeedCursor(final long id) {
@@ -1417,17 +1234,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
- + " 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
+ + "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEEDS
- + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
- + " WHERE " + TABLE_NAME_FEED_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);
}
@@ -1700,13 +1512,35 @@ public class PodDBAdapter {
}
/**
+ * Called when a database corruption happens
+ */
+ public static class PodDbErrorHandler implements DatabaseErrorHandler {
+ @Override
+ public void onCorruption(SQLiteDatabase db) {
+ Log.e(TAG, "Database corrupted: " + db.getPath());
+
+ File dbPath = new File(db.getPath());
+ File backupFolder = PodDBAdapter.context.getExternalFilesDir(null);
+ File backupFile = new File(backupFolder, "CorruptedDatabaseBackup.db");
+ try {
+ FileUtils.copyFile(dbPath, backupFile);
+ Log.d(TAG, "Dumped database to " + backupFile.getPath());
+ } catch (IOException e) {
+ Log.d(TAG, Log.getStackTraceString(e));
+ }
+
+ new DefaultDatabaseErrorHandler().onCorruption(db); // This deletes the database
+ }
+ }
+
+ /**
* Helper class for opening the Antennapod database.
*/
private static class PodDBHelper extends SQLiteOpenHelper {
- private static final int VERSION = 1060200;
+ private static final int VERSION = 1060596;
- private Context context;
+ private final Context context;
/**
* Constructor.
@@ -1717,7 +1551,7 @@ public class PodDBAdapter {
*/
public PodDBHelper(final Context context, final String name,
final CursorFactory factory) {
- super(context, name, factory, VERSION);
+ super(context, name, factory, VERSION, new PodDbErrorHandler());
this.context = context;
}
@@ -1725,7 +1559,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);
@@ -1733,7 +1566,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);
@@ -1748,263 +1580,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/FeedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java
index 9efc5888f..8f2ce5465 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandler.java
@@ -1,17 +1,19 @@
package de.danoeh.antennapod.core.syndication.handler;
-import de.danoeh.antennapod.core.feed.Feed;
import org.apache.commons.io.input.XmlStreamReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import de.danoeh.antennapod.core.feed.Feed;
+
public class FeedHandler {
public FeedHandlerResult parseFeed(Feed feed) throws SAXException, IOException,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java
index f67721a6e..77300d864 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/FeedHandlerResult.java
@@ -9,8 +9,8 @@ import de.danoeh.antennapod.core.feed.Feed;
*/
public class FeedHandlerResult {
- public Feed feed;
- public Map<String, String> alternateFeedUrls;
+ public final Feed feed;
+ public final Map<String, String> alternateFeedUrls;
public FeedHandlerResult(Feed feed, Map<String, String> alternateFeedUrls) {
this.feed = feed;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
index 66513a12e..1cd05aa26 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/HandlerState.java
@@ -20,29 +20,29 @@ public class HandlerState {
/**
* Feed that the Handler is currently processing.
*/
- protected Feed feed;
+ Feed feed;
/**
* Contains links to related feeds, e.g. feeds with enclosures in other formats. The key of the map is the
* URL of the feed, the value is the title
*/
- protected Map<String, String> alternateUrls;
- protected ArrayList<FeedItem> items;
- protected FeedItem currentItem;
- protected Stack<SyndElement> tagstack;
+ final Map<String, String> alternateUrls;
+ private final ArrayList<FeedItem> items;
+ private FeedItem currentItem;
+ final Stack<SyndElement> tagstack;
/**
* Namespaces that have been defined so far.
*/
- protected Map<String, Namespace> namespaces;
- protected Stack<Namespace> defaultNamespaces;
+ final Map<String, Namespace> namespaces;
+ final Stack<Namespace> defaultNamespaces;
/**
* Buffer for saving characters.
*/
- protected StringBuffer contentBuf;
+ protected StringBuilder contentBuf;
/**
* Temporarily saved objects.
*/
- protected Map<String, Object> tempObjects;
+ private final Map<String, Object> tempObjects;
public HandlerState(Feed feed) {
this.feed = feed;
@@ -97,7 +97,7 @@ public class HandlerState {
return third;
}
- public StringBuffer getContentBuf() {
+ public StringBuilder getContentBuf() {
return contentBuf;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
index ae91c0743..ab66b912b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/handler/SyndHandler.java
@@ -18,10 +18,10 @@ import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
/** Superclass for all SAX Handlers which process Syndication formats */
-public class SyndHandler extends DefaultHandler {
+class SyndHandler extends DefaultHandler {
private static final String TAG = "SyndHandler";
private static final String DEFAULT_PREFIX = "";
- protected HandlerState state;
+ final HandlerState state;
public SyndHandler(Feed feed, TypeGetter.Type type) {
state = new HandlerState(feed);
@@ -33,7 +33,7 @@ public class SyndHandler extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
- state.contentBuf = new StringBuffer();
+ state.contentBuf = new StringBuilder();
Namespace handler = getHandlingNamespace(uri, qName);
if (handler != null) {
SyndElement element = handler.handleElementStart(localName, state,
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 3da9251d9..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
@@ -4,8 +4,9 @@ import de.danoeh.antennapod.core.syndication.handler.TypeGetter.Type;
public class UnsupportedFeedtypeException extends Exception {
private static final long serialVersionUID = 9105878964928170669L;
- private TypeGetter.Type type;
- private String rootElement;
+ private final TypeGetter.Type type;
+ 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 7d60566b2..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,34 +15,27 @@ 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";
public static final String DURATION = "duration";
- public static final String SUBTITLE = "subtitle";
- public static final String SUMMARY = "summary";
+ private static final String SUBTITLE = "subtitle";
+ private static final String SUMMARY = "summary";
@Override
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..a1100a976 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
@@ -6,7 +6,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@@ -77,17 +76,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setMedia(media);
}
- } else if (IMAGE.equals(localName)) {
- if (state.getTagstack().size() >= 1) {
- String parent = state.getTagstack().peek().getName();
- if (CHANNEL.equals(parent)) {
- Feed feed = state.getFeed();
- if(feed != null && feed.getImage() == null) {
- feed.setImage(new FeedImage());
- feed.getImage().setOwner(state.getFeed());
- }
- }
- }
}
return new SyndElement(localName, this);
}
@@ -134,11 +122,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setTitle(title);
} else if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setTitle(title);
- } else if (IMAGE.equals(second) && CHANNEL.equals(third)) {
- if(state.getFeed() != null && state.getFeed().getImage() != null &&
- state.getFeed().getImage().getTitle() == null) {
- state.getFeed().getImage().setTitle(title);
- }
}
} else if (LINK.equals(top)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {
@@ -150,9 +133,8 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (URL.equals(top) && IMAGE.equals(second) && CHANNEL.equals(third)) {
// prefer itunes:image
- if(state.getFeed() != null && state.getFeed().getImage() != null &&
- state.getFeed().getImage().getDownload_url() == null) {
- state.getFeed().getImage().setDownload_url(content);
+ if (state.getFeed() != null) {
+ state.getFeed().setImageUrl(content);
}
} else if (DESCR.equals(localName)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
index 703817a35..45266569a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSSimpleChapters.java
@@ -17,11 +17,11 @@ public class NSSimpleChapters extends Namespace {
public static final String NSTAG = "psc|sc";
public static final String NSURI = "http://podlove.org/simple-chapters";
- public static final String CHAPTERS = "chapters";
- public static final String CHAPTER = "chapter";
- public static final String START = "start";
- public static final String TITLE = "title";
- public static final String HREF = "href";
+ private static final String CHAPTERS = "chapters";
+ private static final String CHAPTER = "chapter";
+ private static final String START = "start";
+ private static final String TITLE = "title";
+ private static final String HREF = "href";
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
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 cf118d202..1836bbec1 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
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.core.syndication.namespace;
-import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import org.xml.sax.Attributes;
+import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+
public abstract class Namespace {
public static final String NSTAG = null;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java
index 8adcd2086..ba1b8ba5c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/SyndElement.java
@@ -2,8 +2,8 @@ package de.danoeh.antennapod.core.syndication.namespace;
/** Defines a XML Element that is pushed on the tagstack */
public class SyndElement {
- protected String name;
- protected Namespace namespace;
+ private final String name;
+ private final Namespace namespace;
public SyndElement(String name, Namespace namespace) {
this.name = name;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java
index 43fe0edb7..b512dce3f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomText.java
@@ -1,16 +1,17 @@
package de.danoeh.antennapod.core.syndication.namespace.atom;
+import org.apache.commons.text.StringEscapeUtils;
+
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
-import org.apache.commons.lang3.StringEscapeUtils;
/** Represents Atom Element which contains text (content, title, summary). */
public class AtomText extends SyndElement {
public static final String TYPE_TEXT = "text";
- public static final String TYPE_HTML = "html";
- public static final String TYPE_XHTML = "xhtml";
+ private static final String TYPE_HTML = "html";
+ private static final String TYPE_XHTML = "xhtml";
- private String type;
+ private final String type;
private String content;
public AtomText(String name, Namespace namespace, String type) {
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 cfb20d578..aab1b1a5b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/atom/NSAtom.java
@@ -5,7 +5,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
-import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@@ -64,8 +63,8 @@ public class NSAtom extends Namespace {
private static final String isText = TITLE + "|" + CONTENT + "|"
+ SUBTITLE + "|" + SUMMARY;
- public static final String isFeed = FEED + "|" + NSRSS20.CHANNEL;
- public static final String isFeedItem = ENTRY + "|" + NSRSS20.ITEM;
+ private static final String isFeed = FEED + "|" + NSRSS20.CHANNEL;
+ private static final String isFeedItem = ENTRY + "|" + NSRSS20.ITEM;
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
@@ -210,10 +209,10 @@ public class NSAtom extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (PUBLISHED.equals(top) && ENTRY.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
- } else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImage() == null) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ } else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImageUrl() == null) {
+ state.getFeed().setImageUrl(content);
} else if (IMAGE_ICON.equals(top) && state.getFeed() != null) {
- state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
+ state.getFeed().setImageUrl(content);
} else if (AUTHOR_NAME.equals(top) && AUTHOR.equals(second) &&
state.getFeed() != null && state.getCurrentItem() == null) {
String currentName = state.getFeed().getAuthor();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java
index 70a180913..b513fbe99 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
@@ -71,8 +71,8 @@ public final class Converter {
int m = rest / MINUTES_MIL;
rest -= m * MINUTES_MIL;
int s = rest / SECONDS_MIL;
-
- return String.format("%02d:%02d:%02d", h, m, s);
+
+ return String.format(Locale.getDefault(), "%02d:%02d:%02d", h, m, s);
}
/** Converts milliseconds to a string containing hours and minutes */
@@ -81,7 +81,7 @@ public final class Converter {
int rest = duration - h * HOURS_MIL;
int m = rest / MINUTES_MIL;
- return String.format("%02d:%02d", h, m);
+ return String.format(Locale.getDefault(), "%02d:%02d", h, m);
}
/** Converts long duration string (HH:MM:SS) to milliseconds. */
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 783293a3e..101992e8c 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
@@ -87,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);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
index 7779158e5..0c9989b43 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.util;
import android.content.Context;
+
import de.danoeh.antennapod.core.R;
/** Utility class for Download Errors. */
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java b/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java
index f432424f8..69dc38895 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DuckType.java
@@ -92,7 +92,7 @@ public class DuckType {
* false otherwise.
*/
@SuppressWarnings("rawtypes")
- public boolean quacksLikeA(Class iface) {
+ private boolean quacksLikeA(Class iface) {
for (Method method : iface.getMethods()) {
if (findMethodBySignature(method) == null) {
return false;
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 89edd7dbe..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;
-
-public 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 d0f782fca..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
@@ -76,4 +76,18 @@ public class FeedItemUtil {
return false;
}
+ /**
+ * Get the link for the feed item for the purpose of Share. It fallbacks to
+ * use the feed's link if the named feed item has no link.
+ */
+ public static String getLinkWithFallback(FeedItem item) {
+ if (item == null) {
+ return null;
+ } else if (item.getLink() != null) {
+ return item.getLink();
+ } else if (item.getFeed() != null) {
+ return item.getFeed().getLink();
+ }
+ return null;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
new file mode 100644
index 000000000..afaf13390
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedUpdateUtils.java
@@ -0,0 +1,31 @@
+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() {}
+
+ public static void startAutoUpdate(Context context, Runnable callback) {
+ try {
+ with().pollInterval(1, TimeUnit.SECONDS)
+ .await()
+ .atMost(10, TimeUnit.SECONDS)
+ .until(() -> NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed());
+ DBTasks.refreshAllFeeds(context, null, callback);
+ } 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/FeedtitleComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java
index bf14cd23e..29d095cd2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedtitleComparator.java
@@ -1,11 +1,11 @@
package de.danoeh.antennapod.core.util;
-import de.danoeh.antennapod.core.feed.Feed;
-
import java.util.Comparator;
+import de.danoeh.antennapod.core.feed.Feed;
+
/** Compares the title of two feeds for sorting. */
-public class FeedtitleComparator implements Comparator<Feed> {
+class FeedtitleComparator implements Comparator<Feed> {
@Override
public int compare(Feed lhs, Feed rhs) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java b/core/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java
index a93dd8ee3..e99461806 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/FileNameGenerator.java
@@ -3,7 +3,8 @@ package de.danoeh.antennapod.core.util;
import android.text.TextUtils;
import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.text.RandomStringGenerator;
+
/** Generates valid filenames for a given string. */
public class FileNameGenerator {
@@ -34,7 +35,11 @@ public class FileNameGenerator {
}
String filename = buf.toString().trim();
if(TextUtils.isEmpty(filename)) {
- return RandomStringUtils.randomAlphanumeric(8);
+ return new RandomStringGenerator.Builder()
+ .withinRange('0', 'z')
+ .filteredBy(Character::isLetterOrDigit)
+ .build()
+ .generate(8);
}
return filename;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
index f48b9169b..1da5417c1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
@@ -8,7 +8,7 @@ import java.util.Arrays;
public final class IntList {
private int[] values;
- protected int size;
+ private int size;
/**
* Constructs an empty instance with a default initial capacity.
@@ -22,7 +22,7 @@ public final class IntList {
*
* @param initialCapacity {@code >= 0;} initial capacity of the list
*/
- public IntList(int initialCapacity) {
+ private IntList(int initialCapacity) {
if(initialCapacity < 0) {
throw new IllegalArgumentException("initial capacity must be 0 or higher");
}
@@ -200,7 +200,7 @@ public final class IntList {
* @param value value to find
* @return index of value or -1
*/
- public int indexOf(int value) {
+ private int indexOf(int value) {
for (int i = 0; i < size; i++) {
if (values[i] == value) {
return i;
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 9e35833da..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
@@ -24,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 970210ec3..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
@@ -10,7 +10,7 @@ public class LangUtils {
public static final Charset UTF_8 = Charset.forName("UTF-8");
- private static ArrayMap<String, String> languages;
+ private static final ArrayMap<String, String> languages;
static {
languages = new ArrayMap<>();
languages.put("af", "Afrikaans");
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
index c5ac44bf5..78ed002ac 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
@@ -88,7 +88,7 @@ public class LongIntMap {
/**
* Removes the mapping at the given index.
*/
- public void removeAt(int index) {
+ private void removeAt(int index) {
System.arraycopy(keys, index + 1, keys, index, size - (index + 1));
System.arraycopy(values, index + 1, values, index, size - (index + 1));
size--;
@@ -130,7 +130,7 @@ public class LongIntMap {
* smallest key and <code>keyAt(size()-1)</code> will return the largest
* key.</p>
*/
- public long keyAt(int index) {
+ private long keyAt(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
@@ -150,7 +150,7 @@ public class LongIntMap {
* smallest key and <code>valueAt(size()-1)</code> will return the value
* associated with the largest key.</p>
*/
- public int valueAt(int index) {
+ private int valueAt(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("n >= size()");
} else if(index < 0) {
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 34ad8b8a2..49709bb53 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
@@ -8,6 +8,7 @@ import android.net.wifi.WifiManager;
import android.support.v4.net.ConnectivityManagerCompat;
import android.text.TextUtils;
import android.util.Log;
+
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -54,7 +55,7 @@ public class NetworkUtils {
Log.d(TAG, "Auto-dl filter is disabled");
return true;
} else {
- WifiManager wm = (WifiManager) context
+ WifiManager wm = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();
List<String> selectedNetworks = Arrays
@@ -93,7 +94,7 @@ public class NetworkUtils {
return UserPreferences.isAllowMobileUpdate() || !NetworkUtils.isNetworkMetered();
}
- public static boolean isNetworkMetered() {
+ private static boolean isNetworkMetered() {
ConnectivityManager connManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
return ConnectivityManagerCompat.isActiveNetworkMetered(connManager);
@@ -103,7 +104,7 @@ public class NetworkUtils {
* Returns the SSID of the wifi connection, or <code>null</code> if there is no wifi.
*/
public static String getWifiSsid() {
- WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo != null) {
return wifiInfo.getSSID();
@@ -112,63 +113,60 @@ public class NetworkUtils {
}
public static Observable<Long> getFeedMediaSizeObservable(FeedMedia media) {
- return Observable.create(new Observable.OnSubscribe<Long>() {
- @Override
- public void call(Subscriber<? super Long> subscriber) {
- if (!NetworkUtils.isDownloadAllowed()) {
+ return Observable.create((Observable.OnSubscribe<Long>) subscriber -> {
+ if (!NetworkUtils.isDownloadAllowed()) {
+ subscriber.onNext(0L);
+ subscriber.onCompleted();
+ return;
+ }
+ long size = Integer.MIN_VALUE;
+ if (media.isDownloaded()) {
+ File mediaFile = new File(media.getLocalMediaUrl());
+ if (mediaFile.exists()) {
+ size = mediaFile.length();
+ }
+ } else if (!media.checkedOnSizeButUnknown()) {
+ // only query the network if we haven't already checked
+
+ String url = media.getDownload_url();
+ if(TextUtils.isEmpty(url)) {
subscriber.onNext(0L);
subscriber.onCompleted();
return;
}
- long size = Integer.MIN_VALUE;
- if (media.isDownloaded()) {
- File mediaFile = new File(media.getLocalMediaUrl());
- if (mediaFile.exists()) {
- size = mediaFile.length();
- }
- } else if (!media.checkedOnSizeButUnknown()) {
- // only query the network if we haven't already checked
-
- String url = media.getDownload_url();
- if(TextUtils.isEmpty(url)) {
- subscriber.onNext(0L);
- subscriber.onCompleted();
- return;
- }
- OkHttpClient client = AntennapodHttpClient.getHttpClient();
- Request.Builder httpReq = new Request.Builder()
- .url(url)
- .header("Accept-Encoding", "identity")
- .head();
- try {
- Response response = client.newCall(httpReq.build()).execute();
- if (response.isSuccessful()) {
- String contentLength = response.header("Content-Length");
- try {
- size = Integer.parseInt(contentLength);
- } catch (NumberFormatException e) {
- Log.e(TAG, Log.getStackTraceString(e));
- }
+ OkHttpClient client = AntennapodHttpClient.getHttpClient();
+ Request.Builder httpReq = new Request.Builder()
+ .url(url)
+ .header("Accept-Encoding", "identity")
+ .head();
+ try {
+ Response response = client.newCall(httpReq.build()).execute();
+ if (response.isSuccessful()) {
+ String contentLength = response.header("Content-Length");
+ try {
+ size = Integer.parseInt(contentLength);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
}
- } catch (IOException e) {
- subscriber.onNext(0L);
- subscriber.onCompleted();
- Log.e(TAG, Log.getStackTraceString(e));
- return; // better luck next time
}
+ } catch (IOException e) {
+ subscriber.onNext(0L);
+ subscriber.onCompleted();
+ Log.e(TAG, Log.getStackTraceString(e));
+ return; // better luck next time
}
- Log.d(TAG, "new size: " + size);
- if (size <= 0) {
- // they didn't tell us the size, but we don't want to keep querying on it
- media.setCheckedOnSizeButUnknown();
- } else {
- media.setSize(size);
- }
- subscriber.onNext(size);
- subscriber.onCompleted();
- DBWriter.setFeedMedia(media);
}
+ Log.d(TAG, "new size: " + size);
+ if (size <= 0) {
+ // they didn't tell us the size, but we don't want to keep querying on it
+ media.setCheckedOnSizeButUnknown();
+ } else {
+ media.setSize(size);
+ }
+ subscriber.onNext(size);
+ subscriber.onCompleted();
+ DBWriter.setFeedMedia(media);
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Permutor.java b/core/src/main/java/de/danoeh/antennapod/core/util/Permutor.java
new file mode 100644
index 000000000..7d6e20ab1
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/Permutor.java
@@ -0,0 +1,17 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.List;
+
+/**
+ * Interface for passing around list permutor method. This is used for cases where a simple comparator
+ * won't work (e.g. Random, Smart Shuffle, etc).
+ *
+ * @param <E> the type of elements in the list
+ */
+public interface Permutor<E> {
+ /**
+ * Reorders the specified list.
+ * @param queue A (modifiable) list of elements to be reordered
+ */
+ void reorder(List<E> queue);
+}
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/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
index c3b4c0e15..5c827dfe9 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java
@@ -2,7 +2,12 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -20,11 +25,15 @@ public class QueueSorter {
DURATION_ASC,
DURATION_DESC,
FEED_TITLE_ASC,
- FEED_TITLE_DESC
+ FEED_TITLE_DESC,
+ RANDOM,
+ SMART_SHUFFLE_ASC,
+ SMART_SHUFFLE_DESC
}
public static void sort(final Context context, final Rule rule, final boolean broadcastUpdate) {
Comparator<FeedItem> comparator = null;
+ Permutor<FeedItem> permutor = null;
switch (rule) {
case EPISODE_TITLE_ASC:
@@ -68,11 +77,109 @@ public class QueueSorter {
case FEED_TITLE_DESC:
comparator = (f1, f2) -> f2.getFeed().getTitle().compareTo(f1.getFeed().getTitle());
break;
+ case RANDOM:
+ permutor = Collections::shuffle;
+ break;
+ case SMART_SHUFFLE_ASC:
+ permutor = (queue) -> smartShuffle(queue, true);
+ break;
+ case SMART_SHUFFLE_DESC:
+ permutor = (queue) -> smartShuffle(queue, false);
+ break;
default:
}
if (comparator != null) {
DBWriter.sortQueue(comparator, broadcastUpdate);
+ } else if (permutor != null) {
+ DBWriter.reorderQueue(permutor, broadcastUpdate);
+ }
+ }
+
+ /**
+ * Implements a reordering by pubdate that avoids consecutive episodes from the same feed in
+ * the queue.
+ *
+ * A listener might want to hear episodes from any given feed in pubdate order, but would
+ * prefer a more balanced ordering that avoids having to listen to clusters of consecutive
+ * episodes from the same feed. This is what "Smart Shuffle" tries to accomplish.
+ *
+ * The Smart Shuffle algorithm involves choosing episodes (in round-robin fashion) from a
+ * collection of individual, pubdate-sorted lists that each contain only items from a specific
+ * feed.
+ *
+ * Of course, clusters of consecutive episodes <i>at the end of the queue</i> may be
+ * unavoidable. This seems unlikely to be an issue for most users who presumably maintain
+ * large queues with new episodes continuously being added.
+ *
+ * For example, given a queue containing three episodes each from three different feeds
+ * (A, B, and C), a simple pubdate sort might result in a queue that looks like the following:
+ *
+ * B1, B2, B3, A1, A2, C1, C2, C3, A3
+ *
+ * (note that feed B episodes were all published before the first feed A episode, so a simple
+ * pubdate sort will often result in significant clustering of episodes from a single feed)
+ *
+ * Using Smart Shuffle, the resulting queue would look like the following:
+ *
+ * A1, B1, C1, A2, B2, C2, A3, B3, C3
+ *
+ * (note that episodes above <i>aren't strictly ordered in terms of pubdate</i>, but episodes
+ * of each feed <b>do</b> appear in pubdate order)
+ *
+ * @param queue A (modifiable) list of FeedItem elements to be reordered.
+ * @param ascending {@code true} to use ascending pubdate in the reordering;
+ * {@code false} for descending.
+ */
+ private static void smartShuffle(List<FeedItem> queue, boolean ascending) {
+
+ // Divide FeedItems into lists by feed
+
+ Map<Long, List<FeedItem>> map = new HashMap<>();
+
+ while (!queue.isEmpty()) {
+ FeedItem item = queue.remove(0);
+ Long id = item.getFeedId();
+ if (!map.containsKey(id)) {
+ map.put(id, new ArrayList<>());
+ }
+ map.get(id).add(item);
+ }
+
+ // Sort each individual list by PubDate (ascending/descending)
+
+ Comparator<FeedItem> itemComparator = ascending
+ ? (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate())
+ : (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate());
+
+ for (Long id : map.keySet()) {
+ Collections.sort(map.get(id), itemComparator);
+ }
+
+ // Create a list of the individual FeedItems lists, and sort it by feed title (ascending).
+ // Doing this ensures that the feed order we use is predictable/deterministic.
+
+ List<List<FeedItem>> feeds = new ArrayList<>(map.values());
+ Collections.sort(feeds,
+ // (we use a desc sort here, since we're iterating back-to-front below)
+ (f1, f2) -> f2.get(0).getFeed().getTitle().compareTo(f1.get(0).getFeed().getTitle()));
+
+ // Cycle through the (sorted) feed lists in a round-robin fashion, removing the first item
+ // and adding it back into to the original queue
+
+ while (!feeds.isEmpty()) {
+ // Iterate across the (sorted) list of feeds, removing the first item in each, and
+ // appending it to the queue. Note that we're iterating back-to-front here, since we
+ // will be deleting feed lists as they become empty.
+ for (int i = feeds.size() - 1; i >= 0; --i) {
+ List<FeedItem> items = feeds.get(i);
+ queue.add(items.remove(0));
+ // Removed the last item in this particular feed? Then remove this feed from the
+ // list of feeds.
+ if (items.isEmpty()) {
+ feeds.remove(i);
+ }
+ }
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
index 001bd6a2c..5ae00460e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.content.Intent;
-
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -10,14 +9,14 @@ import android.os.Build;
import android.support.v4.content.FileProvider;
import android.util.Log;
+import java.io.File;
+import java.util.List;
+
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
-import java.io.File;
-import java.util.List;
-
/** Utility methods for sharing data */
public class ShareUtils {
private static final String TAG = "ShareUtils";
@@ -51,11 +50,15 @@ public class ShareUtils {
return item.getFeed().getTitle() + ": " + item.getTitle();
}
+ public static boolean hasLinkToShare(FeedItem item) {
+ return FeedItemUtil.getLinkWithFallback(item) != null;
+ }
+
public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
- String text = getItemShareText(item) + " " + item.getLink();
+ String text = getItemShareText(item) + " " + FeedItemUtil.getLinkWithFallback(item);
if(withPosition) {
int pos = item.getMedia().getPosition();
- text = item.getLink() + " [" + Converter.getDurationStringLong(pos) + "]";
+ text += " [" + Converter.getDurationStringLong(pos) + "]";
}
shareLink(context, text);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
index 1d5fb2645..031eaed49 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/ThemeUtils.java
@@ -1,7 +1,12 @@
package de.danoeh.antennapod.core.util;
+import android.content.Context;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
import android.util.Log;
+import android.util.TypedValue;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -14,6 +19,8 @@ public class ThemeUtils {
int theme = UserPreferences.getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.color.selection_background_color_dark;
+ } else if (theme == R.style.Theme_AntennaPod_TrueBlack){
+ return R.color.selection_background_color_trueblack;
} else if (theme == R.style.Theme_AntennaPod_Light) {
return R.color.selection_background_color_light;
} else {
@@ -22,4 +29,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/comparator/ChapterStartTimeComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java
index 5274ffc9e..8bd23c2ed 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/ChapterStartTimeComparator.java
@@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.util.comparator;
-import de.danoeh.antennapod.core.feed.Chapter;
-
import java.util.Comparator;
+import de.danoeh.antennapod.core.feed.Chapter;
+
public class ChapterStartTimeComparator implements Comparator<Chapter> {
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java
index ebdbfe2a5..868f3b835 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/DownloadStatusComparator.java
@@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.util.comparator;
-import de.danoeh.antennapod.core.service.download.DownloadStatus;
-
import java.util.Comparator;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+
/** Compares the completion date of two Downloadstatus objects. */
public class DownloadStatusComparator implements Comparator<DownloadStatus> {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
index a1f3ec699..a96eda3c5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
@@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.util.comparator;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
import java.util.Comparator;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
/** Compares the pubDate of two FeedItems for sorting*/
public class FeedItemPubdateComparator implements Comparator<FeedItem> {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java
index 84d244660..d65eb3e0b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/PlaybackCompletionDateComparator.java
@@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.util.comparator;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
import java.util.Comparator;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
public class PlaybackCompletionDateComparator implements Comparator<FeedItem> {
public int compare(FeedItem lhs, FeedItem rhs) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java
index d23901a45..56a684475 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/SearchResultValueComparator.java
@@ -1,10 +1,10 @@
package de.danoeh.antennapod.core.util.comparator;
+import java.util.Comparator;
+
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.SearchResult;
-import java.util.Comparator;
-
public class SearchResultValueComparator implements Comparator<SearchResult> {
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
new file mode 100644
index 000000000..ad723c685
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java
@@ -0,0 +1,155 @@
+package de.danoeh.antennapod.core.util.download;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.service.FeedUpdateJobService;
+
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+public class AutoUpdateManager {
+ private static final int JOB_ID_FEED_UPDATE = 42;
+ private static final String TAG = "AutoUpdateManager";
+
+ private AutoUpdateManager() {
+
+ }
+
+ /**
+ * Sets the interval in which the feeds are refreshed automatically
+ */
+ public static void restartUpdateIntervalAlarm(Context context, long triggerAtMillis, long intervalMillis) {
+ Log.d(TAG, "Restarting update alarm.");
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ restartJobServiceInterval(context, intervalMillis);
+ } else {
+ restartAlarmManagerInterval(context, triggerAtMillis, intervalMillis);
+ }
+ }
+
+ /**
+ * Sets time of day the feeds are refreshed automatically
+ */
+ public static void restartUpdateTimeOfDayAlarm(Context context, int hoursOfDay, int minute) {
+ Log.d(TAG, "Restarting update alarm.");
+
+ Calendar now = Calendar.getInstance();
+ Calendar alarm = (Calendar)now.clone();
+ alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
+ alarm.set(Calendar.MINUTE, minute);
+ if (alarm.before(now) || alarm.equals(now)) {
+ alarm.add(Calendar.DATE, 1);
+ }
+
+ if (Build.VERSION.SDK_INT >= 24) {
+ long triggerAtMillis = alarm.getTimeInMillis() - now.getTimeInMillis();
+ restartJobServiceTriggerAt(context, triggerAtMillis);
+ } else {
+ restartAlarmManagerTimeOfDay(context, alarm);
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static JobInfo.Builder getFeedUpdateJobBuilder(Context context) {
+ ComponentName serviceComponent = new ComponentName(context, FeedUpdateJobService.class);
+ JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_FEED_UPDATE, serviceComponent);
+ builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ builder.setPersisted(true);
+ return builder;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ private static void restartJobServiceInterval(Context context, long intervalMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Log.d(TAG, "JobScheduler was null.");
+ return;
+ }
+
+ JobInfo oldJob = jobScheduler.getPendingJob(JOB_ID_FEED_UPDATE);
+ if (oldJob != null && oldJob.getIntervalMillis() == intervalMillis) {
+ Log.d(TAG, "JobScheduler was already set at interval " + intervalMillis + ", ignoring.");
+ return;
+ }
+
+ JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
+ builder.setPeriodic(intervalMillis);
+ jobScheduler.cancel(JOB_ID_FEED_UPDATE);
+
+ if (intervalMillis <= 0) {
+ Log.d(TAG, "Automatic update was deactivated");
+ return;
+ }
+
+ jobScheduler.schedule(builder.build());
+ Log.d(TAG, "JobScheduler was set at interval " + intervalMillis);
+ }
+
+ private static void restartAlarmManagerInterval(Context context, long triggerAtMillis, long intervalMillis) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ if (alarmManager == null) {
+ Log.d(TAG, "AlarmManager was null");
+ return;
+ }
+
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, FeedUpdateReceiver.class), 0);
+ alarmManager.cancel(updateIntent);
+
+ if (intervalMillis <= 0) {
+ Log.d(TAG, "Automatic update was deactivated");
+ return;
+ }
+
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + triggerAtMillis,
+ updateIntent);
+ Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
+ private static void restartJobServiceTriggerAt(Context context, long triggerAtMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Log.d(TAG, "JobScheduler was null.");
+ return;
+ }
+
+ JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
+ builder.setMinimumLatency(triggerAtMillis);
+ jobScheduler.cancel(JOB_ID_FEED_UPDATE);
+ jobScheduler.schedule(builder.build());
+ Log.d(TAG, "JobScheduler was set for " + triggerAtMillis);
+ }
+
+ private static void restartAlarmManagerTimeOfDay(Context context, Calendar alarm) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ if (alarmManager == null) {
+ Log.d(TAG, "AlarmManager was null");
+ return;
+ }
+
+ PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(context, FeedUpdateReceiver.class), 0);
+ alarmManager.cancel(updateIntent);
+
+ Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
+ alarmManager.set(AlarmManager.RTC_WAKEUP,
+ alarm.getTimeInMillis(),
+ updateIntent);
+ Log.d(TAG, "Changed alarm to new time of day " + alarm.get(Calendar.HOUR_OF_DAY) + ":" + alarm.get(Calendar.MINUTE));
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java b/core/src/main/java/de/danoeh/antennapod/core/util/exception/MediaFileNotFoundException.java
index 287fe1100..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
@@ -5,18 +5,13 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
public class MediaFileNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
- private FeedMedia media;
+ private final FeedMedia media;
public MediaFileNotFoundException(String msg, FeedMedia media) {
super(msg);
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 2103ae3b2..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
@@ -10,7 +10,7 @@ import de.danoeh.antennapod.core.BuildConfig;
/** Ensures that only one instance of the FlattrService class exists at a time */
-public class FlattrServiceCreator {
+class FlattrServiceCreator {
private FlattrServiceCreator(){}
public static final String TAG = "FlattrServiceCreator";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java
index d82171d1a..40a9fc7d5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrStatus.java
@@ -3,9 +3,9 @@ package de.danoeh.antennapod.core.util.flattr;
import java.util.Calendar;
public class FlattrStatus {
- public static final int STATUS_UNFLATTERED = 0;
+ private static final int STATUS_UNFLATTERED = 0;
public static final int STATUS_QUEUE = 1;
- public static final int STATUS_FLATTRED = 2;
+ private static final int STATUS_FLATTRED = 2;
private int status = STATUS_UNFLATTERED;
private Calendar lastFlattred;
@@ -38,7 +38,7 @@ public class FlattrStatus {
status = STATUS_QUEUE;
}
- public void fromLong(long status) {
+ private void fromLong(long status) {
if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE)
this.status = (int) status;
else {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java
index 515028ab6..d5bb88771 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrThing.java
@@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.util.flattr;
public interface FlattrThing {
- public String getTitle();
- public String getPaymentLink();
- public FlattrStatus getFlattrStatus();
+ String getTitle();
+ String getPaymentLink();
+ FlattrStatus getFlattrStatus();
}
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 558485ce3..dfb5484cd 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
@@ -101,7 +101,7 @@ public class FlattrUtils {
cachedToken = token;
}
- public static void deleteToken() {
+ private static void deleteToken() {
Log.d(TAG, "Deleting flattr token");
storeToken(null);
}
@@ -174,17 +174,11 @@ public class FlattrUtils {
// ------------------------------------------------ DIALOGS
- public static void showRevokeDialog(final Context context) {
+ private static void showRevokeDialog(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.access_revoked_title);
builder.setMessage(R.string.access_revoked_info);
- builder.setNeutralButton(android.R.string.ok, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
+ builder.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.cancel());
builder.create().show();
}
@@ -199,27 +193,15 @@ public class FlattrUtils {
builder.setTitle(R.string.no_flattr_token_title);
builder.setMessage(R.string.no_flattr_token_msg);
builder.setPositiveButton(R.string.authenticate_now_label,
- new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- context.startActivity(
- ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context));
- }
-
- }
+ (dialog, which) -> context.startActivity(
+ ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context))
);
builder.setNegativeButton(R.string.visit_website_label,
- new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Uri uri = Uri.parse(url);
- context.startActivity(new Intent(Intent.ACTION_VIEW,
- uri));
- }
-
+ (dialog, which) -> {
+ Uri uri = Uri.parse(url);
+ context.startActivity(new Intent(Intent.ACTION_VIEW,
+ uri));
}
);
builder.create().show();
@@ -233,13 +215,7 @@ public class FlattrUtils {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.error_label);
builder.setMessage(msg);
- builder.setNeutralButton(android.R.string.ok, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
+ builder.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.cancel());
builder.create().show();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java
index 2c178496e..43cd5f170 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/SimpleFlattrThing.java
@@ -24,7 +24,7 @@ public class SimpleFlattrThing implements FlattrThing {
return this.status;
}
- private String title;
- private String url;
- private FlattrStatus status;
+ private final String title;
+ private final String url;
+ private final FlattrStatus status;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
new file mode 100644
index 000000000..2a537dc62
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
@@ -0,0 +1,64 @@
+package de.danoeh.antennapod.core.util.gui;
+
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import de.danoeh.antennapod.core.R;
+
+public class NotificationUtils {
+ public static final String CHANNEL_ID_USER_ACTION = "user_action";
+ public static final String CHANNEL_ID_DOWNLOADING = "downloading";
+ public static final String CHANNEL_ID_PLAYING = "playing";
+ public static final String CHANNEL_ID_ERROR = "error";
+
+ public static void createChannels(Context context) {
+ if (android.os.Build.VERSION.SDK_INT < 26) {
+ return;
+ }
+ NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (mNotificationManager != null) {
+ mNotificationManager.createNotificationChannel(createChannelUserAction(context));
+ mNotificationManager.createNotificationChannel(createChannelDownloading(context));
+ mNotificationManager.createNotificationChannel(createChannelPlaying(context));
+ mNotificationManager.createNotificationChannel(createChannelError(context));
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelUserAction(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_USER_ACTION,
+ c.getString(R.string.notification_channel_user_action), NotificationManager.IMPORTANCE_HIGH);
+ mChannel.setDescription(c.getString(R.string.notification_channel_user_action_description));
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelDownloading(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_DOWNLOADING,
+ c.getString(R.string.notification_channel_downloading), NotificationManager.IMPORTANCE_LOW);
+ mChannel.setDescription(c.getString(R.string.notification_channel_downloading_description));
+ mChannel.setShowBadge(false);
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelPlaying(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_PLAYING,
+ c.getString(R.string.notification_channel_playing), NotificationManager.IMPORTANCE_LOW);
+ mChannel.setDescription(c.getString(R.string.notification_channel_playing_description));
+ mChannel.setShowBadge(false);
+ return mChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelError(Context c) {
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_ERROR,
+ c.getString(R.string.notification_channel_error), NotificationManager.IMPORTANCE_HIGH);
+ mChannel.setDescription(c.getString(R.string.notification_channel_error_description));
+ return mChannel;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/PictureInPictureUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/PictureInPictureUtil.java
new file mode 100644
index 000000000..f763653a1
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/PictureInPictureUtil.java
@@ -0,0 +1,27 @@
+package de.danoeh.antennapod.core.util.gui;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+public class PictureInPictureUtil {
+ private PictureInPictureUtil() {
+ }
+
+ public static boolean supportsPictureInPicture(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ PackageManager packageManager = activity.getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean isInPictureInPictureMode(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && supportsPictureInPicture(activity)) {
+ return activity.isInPictureInPictureMode();
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
index 51fa5b4d6..f681b8062 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ChapterReader.java
@@ -8,7 +8,6 @@ import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
@@ -44,7 +43,7 @@ public class ChapterReader extends ID3Reader {
currentChapter = null;
}
}
- StringBuffer elementId = new StringBuffer();
+ StringBuilder elementId = new StringBuilder();
readISOString(elementId, input, Integer.MAX_VALUE);
char[] startTimeSource = readBytes(input, 4);
long startTime = ((int) startTimeSource[0] << 24)
@@ -55,7 +54,7 @@ public class ChapterReader extends ID3Reader {
return ID3Reader.ACTION_DONT_SKIP;
case FRAME_ID_TITLE:
if (currentChapter != null && currentChapter.getTitle() == null) {
- StringBuffer title = new StringBuffer();
+ StringBuilder title = new StringBuilder();
readString(title, input, header.getSize());
currentChapter
.setTitle(title.toString());
@@ -68,7 +67,7 @@ public class ChapterReader extends ID3Reader {
if (currentChapter != null) {
// skip description
int descriptionLength = readString(null, input, header.getSize());
- StringBuffer link = new StringBuffer();
+ StringBuilder link = new StringBuilder();
readISOString(link, input, header.getSize() - descriptionLength);
String decodedLink = URLDecoder.decode(link.toString(), "UTF-8");
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java
index a238c11e9..7290b9d98 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java
@@ -1,7 +1,5 @@
package de.danoeh.antennapod.core.util.id3reader;
-import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
-import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
@@ -9,6 +7,9 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
+import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
+import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
+
/**
* Reads the ID3 Tag of a given file. In order to use this class, you should
* create a subclass of it and overwrite the onStart* - or onEnd* - methods.
@@ -18,10 +19,10 @@ public class ID3Reader {
private static final int ID3_LENGTH = 3;
private static final int FRAME_ID_LENGTH = 4;
- protected static final int ACTION_SKIP = 1;
- protected static final int ACTION_DONT_SKIP = 2;
+ private static final int ACTION_SKIP = 1;
+ static final int ACTION_DONT_SKIP = 2;
- protected int readerPosition;
+ private int readerPosition;
private static final byte ENCODING_UTF16_WITH_BOM = 1;
private static final byte ENCODING_UTF16_WITHOUT_BOM = 2;
@@ -29,7 +30,7 @@ public class ID3Reader {
private TagHeader tagHeader;
- public ID3Reader() {
+ ID3Reader() {
}
public final void readInputStream(InputStream input) throws IOException,
@@ -91,7 +92,7 @@ public class ID3Reader {
* Read a certain number of bytes from the given input stream. This method
* changes the readerPosition-attribute.
*/
- protected char[] readBytes(InputStream input, int number)
+ char[] readBytes(InputStream input, int number)
throws IOException, ID3ReaderException {
char[] header = new char[number];
for (int i = 0; i < number; i++) {
@@ -110,7 +111,7 @@ public class ID3Reader {
* Skip a certain number of bytes on the given input stream. This method
* changes the readerPosition-attribute.
*/
- protected void skipBytes(InputStream input, int number) throws IOException {
+ void skipBytes(InputStream input, int number) throws IOException {
if (number <= 0) {
number = 1;
}
@@ -169,7 +170,7 @@ public class ID3Reader {
return out;
}
- protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException,
+ protected int readString(StringBuilder buffer, InputStream input, int max) throws IOException,
ID3ReaderException {
if (max > 0) {
char[] encoding = readBytes(input, 1);
@@ -190,9 +191,8 @@ public class ID3Reader {
}
}
- protected int readISOString(StringBuffer buffer, InputStream input, int max)
+ protected int readISOString(StringBuilder buffer, InputStream input, int max)
throws IOException, ID3ReaderException {
-
int bytesRead = 0;
char c;
while (++bytesRead <= max && (c = (char) input.read()) > 0) {
@@ -203,7 +203,7 @@ public class ID3Reader {
return bytesRead;
}
- private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset)
+ private int readUnicodeString(StringBuilder strBuffer, InputStream input, int max, Charset charset)
throws IOException, ID3ReaderException {
byte[] buffer = new byte[max];
int c, cZero = -1;
@@ -230,20 +230,20 @@ public class ID3Reader {
return i;
}
- public int onStartTagHeader(TagHeader header) {
+ int onStartTagHeader(TagHeader header) {
return ACTION_SKIP;
}
- public int onStartFrameHeader(FrameHeader header, InputStream input)
+ int onStartFrameHeader(FrameHeader header, InputStream input)
throws IOException, ID3ReaderException {
return ACTION_SKIP;
}
- public void onEndTag() {
+ void onEndTag() {
}
- public void onNoTagHeaderFound() {
+ void onNoTagHeaderFound() {
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java
index 89eab1398..2f3f378ab 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/FrameHeader.java
@@ -2,7 +2,7 @@ package de.danoeh.antennapod.core.util.id3reader.model;
public class FrameHeader extends Header {
- protected char flags;
+ private final char flags;
public FrameHeader(String id, int size, char flags) {
super(id, size);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java
index 346e2893f..29185748f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/Header.java
@@ -2,10 +2,10 @@ package de.danoeh.antennapod.core.util.id3reader.model;
public abstract class Header {
- protected String id;
- protected int size;
+ final String id;
+ final int size;
- public Header(String id, int size) {
+ Header(String id, int size) {
super();
this.id = id;
this.size = size;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java
index 0a6b8357f..b652a139c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/model/TagHeader.java
@@ -2,8 +2,8 @@ package de.danoeh.antennapod.core.util.id3reader.model;
public class TagHeader extends Header {
- protected char version;
- protected byte flags;
+ private final char version;
+ private final byte flags;
public TagHeader(String id, int size, char version, byte flags) {
super(id, size);
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..16d05dbb9 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,11 +34,6 @@ 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
protected boolean useSonic() {
return UserPreferences.useSonic();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
index c4acdb65e..645bae5f3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
@@ -23,7 +23,7 @@ public class ExternalMedia implements Playable {
public static final String PREF_MEDIA_TYPE = "ExternalMedia.PrefMediaType";
public static final String PREF_LAST_PLAYED_TIME = "ExternalMedia.PrefLastPlayedTime";
- private String source;
+ private final String source;
private String episodeTitle;
private String feedTitle;
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 d67153a4e..a372f4241 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,21 +32,11 @@ public interface IPlayer {
void setAudioStreamType(int streamtype);
- void setScreenOnWhilePlaying(boolean screenOn);
-
void setDataSource(String path) throws IllegalStateException, IOException,
- IllegalArgumentException, SecurityException;
+ 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 setDownmix(boolean enable);
@@ -67,7 +47,5 @@ public interface IPlayer {
void stop();
- public void setVideoScalingMode(int mode);
-
- public void setWakeMode(Context context, 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 6417ec919..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
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.media.MediaPlayer;
+
import de.danoeh.antennapod.core.R;
/** Utility class for MediaPlayer errors. */
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 cb0757522..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
@@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcelable;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
@@ -11,6 +13,7 @@ import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@@ -181,6 +184,23 @@ public interface Playable extends Parcelable,
* Restores a playable object from a sharedPreferences file. This method might load data from the database,
* depending on the type of playable that was restored.
*
+ * @return The restored Playable object
+ */
+ @Nullable
+ public static Playable createInstanceFromPreferences(Context context) {
+ long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
+ if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ return PlayableUtils.createInstanceFromPreferences(context,
+ (int) currentlyPlayingMedia, prefs);
+ }
+ return null;
+ }
+
+ /**
+ * Restores a playable object from a sharedPreferences file. This method might load data from the database,
+ * depending on the type of playable that was restored.
+ *
* @param type An integer that represents the type of the Playable object
* that is restored.
* @param pref The SharedPreferences file from which the Playable object
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index 9d3854f41..31067839a 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -14,6 +14,8 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -40,9 +42,12 @@ 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.playback.Playable.PlayableUtils;
+import rx.Completable;
import rx.Observable;
+import rx.Single;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
+import rx.observers.Subscribers;
import rx.schedulers.Schedulers;
/**
@@ -59,7 +64,7 @@ public abstract class PlaybackController {
private PlaybackService playbackService;
private Playable media;
- private PlayerStatus status;
+ private PlayerStatus status = PlayerStatus.STOPPED;
private final ScheduledThreadPoolExecutor schedExecutor;
private static final int SCHED_EX_POOLSIZE = 1;
@@ -69,6 +74,7 @@ public abstract class PlaybackController {
private boolean mediaInfoLoaded = false;
private boolean released = false;
+ private boolean initialized = false;
private Subscription serviceBinder;
@@ -87,21 +93,27 @@ public abstract class PlaybackController {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
- }, new RejectedExecutionHandler() {
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "Rejected execution of runnable in schedExecutor");
- }
- }
+ }, (r, executor) -> Log.w(TAG, "Rejected execution of runnable in schedExecutor")
);
}
/**
- * Creates a new connection to the playbackService. Should be called in the
- * activity's onResume() method.
+ * Creates a new connection to the playbackService.
*/
public void init() {
+ if (PlaybackService.isRunning) {
+ initServiceRunning();
+ } else {
+ initServiceNotRunning();
+ }
+ }
+
+ private synchronized void initServiceRunning() {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+
activity.registerReceiver(statusUpdate, new IntentFilter(
PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
@@ -173,7 +185,7 @@ public abstract class PlaybackController {
*/
private void bindToService() {
Log.d(TAG, "Trying to connect to service");
- if(serviceBinder != null) {
+ if (serviceBinder != null) {
serviceBinder.unsubscribe();
}
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
@@ -184,7 +196,7 @@ public abstract class PlaybackController {
if (!PlaybackService.started) {
if (intent != null) {
Log.d(TAG, "Calling start service");
- activity.startService(intent);
+ ContextCompat.startForegroundService(activity, intent);
bound = activity.bindService(intent, mConnection, 0);
} else {
status = PlayerStatus.STOPPED;
@@ -204,31 +216,24 @@ public abstract class PlaybackController {
* Returns an intent that starts the PlaybackService and plays the last
* played media or null if no last played media could be found.
*/
- private Intent getPlayLastPlayedMediaIntent() {
+ @Nullable private Intent getPlayLastPlayedMediaIntent() {
Log.d(TAG, "Trying to restore last played media");
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
- activity.getApplicationContext());
- long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
- if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
- Playable media = PlayableUtils.createInstanceFromPreferences(activity,
- (int) currentlyPlayingMedia, prefs);
- if (media != null) {
- Intent serviceIntent = new Intent(activity, PlaybackService.class);
- serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- serviceIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, false);
- serviceIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true);
- boolean fileExists = media.localFileAvailable();
- boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
- if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
- DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
- }
- serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- lastIsStream || !fileExists);
- return serviceIntent;
- }
+ Playable media = PlayableUtils.createInstanceFromPreferences(activity);
+ if (media == null) {
+ Log.d(TAG, "No last played media found");
+ return null;
}
- Log.d(TAG, "No last played media found");
- return null;
+
+ boolean fileExists = media.localFileAvailable();
+ boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
+ if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
+ DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
+ }
+
+ return new PlaybackServiceStarter(activity, media)
+ .startWhenPrepared(false)
+ .shouldStream(lastIsStream || !fileExists)
+ .getIntent();
}
@@ -517,7 +522,7 @@ public abstract class PlaybackController {
"PlaybackService has no media object. Trying to restore last played media.");
Intent serviceIntent = getPlayLastPlayedMediaIntent();
if (serviceIntent != null) {
- activity.startService(serviceIntent);
+ ContextCompat.startForegroundService(activity, serviceIntent);
}
}
*/
@@ -582,6 +587,10 @@ public abstract class PlaybackController {
public void playPause() {
if (playbackService == null) {
+ new PlaybackServiceStarter(activity, media)
+ .startWhenPrepared(true)
+ .streamIfLastWasStream()
+ .start();
Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
return;
}
@@ -615,6 +624,8 @@ public abstract class PlaybackController {
public int getPosition() {
if (playbackService != null) {
return playbackService.getCurrentPosition();
+ } else if (media != null) {
+ return media.getPosition();
} else {
return PlaybackService.INVALID_TIME;
}
@@ -623,12 +634,17 @@ public abstract class PlaybackController {
public int getDuration() {
if (playbackService != null) {
return playbackService.getDuration();
+ } else if (media != null) {
+ return media.getDuration();
} else {
return PlaybackService.INVALID_TIME;
}
}
public Playable getMedia() {
+ if (media == null) {
+ media = PlayableUtils.createInstanceFromPreferences(activity);
+ }
return media;
}
@@ -720,8 +736,13 @@ public abstract class PlaybackController {
}
public boolean isPlayingVideoLocally() {
- return playbackService != null && PlaybackService.getCurrentMediaType() == MediaType.VIDEO
- && !PlaybackService.isCasting();
+ if (PlaybackService.isCasting()) {
+ return false;
+ } else if (playbackService != null) {
+ return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
+ } else {
+ return getMedia() != null && getMedia().getMediaType() == MediaType.VIDEO;
+ }
}
public Pair<Integer, Integer> getVideoSize() {
@@ -761,6 +782,27 @@ public abstract class PlaybackController {
}
}
+ private void initServiceNotRunning() {
+ Single.create(subscriber -> subscriber.onSuccess(getMedia()))
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe((Object media) -> {
+ if (media == null) {
+ return;
+ }
+
+ if (((Playable) 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_circle_outline_80dp);
+ }
+ });
+ }
+
/**
* Refreshes the current position of the media file that is playing.
*/
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
new file mode 100644
index 000000000..3ba553d12
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
@@ -0,0 +1,76 @@
+package de.danoeh.antennapod.core.util.playback;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.support.v4.content.ContextCompat;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+
+public class PlaybackServiceStarter {
+ private final Context context;
+ private final Playable media;
+ private boolean startWhenPrepared = false;
+ private boolean shouldStream = false;
+ private boolean callEvenIfRunning = false;
+ private boolean prepareImmediately = true;
+
+ public PlaybackServiceStarter(Context context, Playable media) {
+ this.context = context;
+ this.media = media;
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter shouldStream(boolean shouldStream) {
+ this.shouldStream = shouldStream;
+ return this;
+ }
+
+ public PlaybackServiceStarter streamIfLastWasStream() {
+ boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
+ return shouldStream(lastIsStream);
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter startWhenPrepared(boolean startWhenPrepared) {
+ this.startWhenPrepared = startWhenPrepared;
+ return this;
+ }
+
+ /**
+ * Default value: false
+ */
+ public PlaybackServiceStarter callEvenIfRunning(boolean callEvenIfRunning) {
+ this.callEvenIfRunning = callEvenIfRunning;
+ return this;
+ }
+
+ /**
+ * Default value: true
+ */
+ public PlaybackServiceStarter prepareImmediately(boolean prepareImmediately) {
+ this.prepareImmediately = prepareImmediately;
+ return this;
+ }
+
+ public Intent getIntent() {
+ Intent launchIntent = new Intent(context, PlaybackService.class);
+ launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared);
+ launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
+ launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, prepareImmediately);
+
+ return launchIntent;
+ }
+
+ public void start() {
+ if (PlaybackService.isRunning && !callEvenIfRunning) {
+ return;
+ }
+ ContextCompat.startForegroundService(context, getIntent());
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java
index efdf46a97..34cfe6d05 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
@@ -14,6 +14,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -42,22 +43,22 @@ public class Timeline {
private final int pageMargin;
public Timeline(Context context, ShownotesProvider shownotesProvider) {
- if (shownotesProvider == null) throw new IllegalArgumentException("shownotesProvider = null");
+ if (shownotesProvider == null) {
+ throw new IllegalArgumentException("shownotesProvider = null");
+ }
this.shownotesProvider = shownotesProvider;
noShownotesLabel = context.getString(R.string.no_shownotes_label);
- TypedArray res = context.getTheme().obtainStyledAttributes(
- new int[]{ android.R.attr.textColorPrimary});
+ TypedArray res = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorPrimary});
@ColorInt int col = res.getColor(0, 0);
colorPrimaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
- Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")";
+ Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")";
res.recycle();
- res = context.getTheme().obtainStyledAttributes(
- new int[]{android.R.attr.textColorSecondary});
+ res = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorSecondary});
col = res.getColor(0, 0);
colorSecondaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," +
- Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")";
+ Color.blue(col) + "," + (Color.alpha(col) / 255.0) + ")";
res.recycle();
pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
@@ -93,9 +94,9 @@ public class Timeline {
return null;
}
- if(TextUtils.isEmpty(shownotes)) {
+ if (TextUtils.isEmpty(shownotes)) {
Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message");
- shownotes ="<html>" +
+ shownotes = "<html>" +
"<head>" +
"<style type='text/css'>" +
"html, body { margin: 0; padding: 0; width: 100%; height: 100%; } " +
@@ -113,15 +114,15 @@ public class Timeline {
}
// replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already
- if(!LINE_BREAK_REGEX.matcher(shownotes).find() && !shownotes.contains("<p>")) {
+ if (!LINE_BREAK_REGEX.matcher(shownotes).find() && !shownotes.contains("<p>")) {
shownotes = shownotes.replace("\n", "<br />");
}
Document document = Jsoup.parse(shownotes);
// apply style
- String styleStr = String.format(WEBVIEW_STYLE, colorPrimaryString, "100%", pageMargin,
- pageMargin, pageMargin, pageMargin);
+ String styleStr = String.format(Locale.getDefault(), WEBVIEW_STYLE, colorPrimaryString, "100%",
+ pageMargin, pageMargin, pageMargin, pageMargin);
document.head().appendElement("style").attr("type", "text/css").text(styleStr);
// apply timecode links
@@ -139,7 +140,7 @@ public class Timeline {
String rep;
if (playable == null || playable.getDuration() > time) {
- rep = String.format(TIMECODE_LINK, time, group);
+ rep = String.format(Locale.getDefault(), TIMECODE_LINK, time, group);
} else {
rep = group;
}
@@ -150,7 +151,7 @@ public class Timeline {
element.html(buffer.toString());
}
}
-
+
return document.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..1d04fb878 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,44 +17,11 @@ 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");
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
index 13cb9f002..c5ad9cfd6 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/FeedDiscoverer.java
@@ -41,7 +41,7 @@ public class FeedDiscoverer {
* @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if
* a title cannot be found).
*/
- public Map<String, String> findLinks(String in, String baseUrl) throws IOException {
+ public Map<String, String> findLinks(String in, String baseUrl) {
return findLinks(Jsoup.parse(in), baseUrl);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
index bd40f398d..cc6a8ec90 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/syndication/HtmlToPlainText.java
@@ -40,9 +40,9 @@ public class HtmlToPlainText {
}
// the formatting rules, implemented in a breadth-first DOM traverse
- private class FormattingVisitor implements NodeVisitor {
+ private static class FormattingVisitor implements NodeVisitor {
- private StringBuilder accum = new StringBuilder(); // holds the accumulated text
+ private final StringBuilder accum = new StringBuilder(); // holds the accumulated text
// hit when the node is first seen
public void head(Node node, int depth) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java
index 4799d3881..cdf171299 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/OggInputStream.java
@@ -6,8 +6,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
-public class OggInputStream extends InputStream {
- private InputStream input;
+class OggInputStream extends InputStream {
+ private final InputStream input;
/** True if OggInputStream is currently inside an Ogg page. */
private boolean isInPage;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
index 6243da5bc..569ff3438 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentChapterReader.java
@@ -1,13 +1,14 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import java.util.ArrayList;
import java.util.List;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
+
public class VorbisCommentChapterReader extends VorbisCommentReader {
private static final String TAG = "VorbisCommentChptrReadr";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java
index 5f9dd0faf..ff7508390 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentHeader.java
@@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
-public class VorbisCommentHeader {
- private String vendorString;
- private long userCommentLength;
+class VorbisCommentHeader {
+ private final String vendorString;
+ private final long userCommentLength;
public VorbisCommentHeader(String vendorString, long userCommentLength) {
super();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
index 49ea18721..55498afcb 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/vorbiscommentreader/VorbisCommentReader.java
@@ -19,29 +19,29 @@ public abstract class VorbisCommentReader {
private static final int PACKET_TYPE_COMMENT = 3;
/** Called when Reader finds identification header. */
- public abstract void onVorbisCommentFound();
+ protected abstract void onVorbisCommentFound();
- public abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header);
+ protected abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header);
/**
* Is called every time the Reader finds a content vector. The handler
* should return true if it wants to handle the content vector.
*/
- public abstract boolean onContentVectorKey(String content);
+ protected abstract boolean onContentVectorKey(String content);
/**
* Is called if onContentVectorKey returned true for the key.
*
* @throws VorbisCommentReaderException
*/
- public abstract void onContentVectorValue(String key, String value)
+ protected abstract void onContentVectorValue(String key, String value)
throws VorbisCommentReaderException;
- public abstract void onNoVorbisCommentFound();
+ protected abstract void onNoVorbisCommentFound();
- public abstract void onEndOfComment();
+ protected abstract void onEndOfComment();
- public abstract void onError(VorbisCommentReaderException exception);
+ protected abstract void onError(VorbisCommentReaderException exception);
public void readInputStream(InputStream input)
throws VorbisCommentReaderException {