summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml2
-rw-r--r--app/build.gradle15
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java102
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java83
-rw-r--r--app/src/main/res/layout/feeditemlist_item.xml2
-rw-r--r--build.gradle5
-rw-r--r--core/build.gradle17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java77
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java70
-rw-r--r--core/src/main/res/values/strings.xml2
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java100
16 files changed, 304 insertions, 218 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index fa0e1cef8..8063f259c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -66,7 +66,7 @@ workflows:
build-steps:
- run:
name: Build free (for F-Droid)
- command: ./gradlew assembleFreeRelease -PdisablePreDex -PfreeBuild
+ command: ./gradlew assembleFreeRelease -PdisablePreDex
static-analysis:
jobs:
diff --git a/app/build.gradle b/app/build.gradle
index dea0e9992..9493a074b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -22,8 +22,8 @@ android {
// "1.2.3-SNAPSHOT" -> 1020300
// "1.2.3-RC4" -> 1020304
// "1.2.3" -> 1020395
- versionCode 2010395
- versionName "2.1.3"
+ versionCode 2010495
+ versionName "2.1.4"
multiDexEnabled false
vectorDrawables.useSupportLibrary true
@@ -156,14 +156,7 @@ android {
}
dependencies {
- freeImplementation project(":core")
- // free build hack: skip some dependencies
- if (!doFreeBuild()) {
- playImplementation project(":core")
- implementation 'com.google.android.play:core:1.8.0'
- } else {
- System.out.println("app: free build hack, skipping some dependencies")
- }
+ implementation project(":core")
implementation project(':ui:app-start-intent')
implementation project(':ui:common')
@@ -200,6 +193,8 @@ dependencies {
implementation 'com.github.ByteHamster:SearchPreference:v2.0.0'
implementation 'com.github.skydoves:balloon:1.1.5'
+ // Non-free dependencies:
+ playImplementation 'com.google.android.play:core:1.8.0'
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
index be9699348..7ee0936d0 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
@@ -1,10 +1,7 @@
package de.danoeh.antennapod.fragment.gpodnet;
-import android.content.Context;
import android.content.Intent;
-import android.os.AsyncTask;
import android.os.Bundle;
-import androidx.fragment.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -13,9 +10,7 @@ import android.widget.Button;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
-import java.util.List;
-
+import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
@@ -25,6 +20,12 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+import java.util.List;
/**
* Displays a list of GPodnetPodcast-Objects in a GridView
@@ -36,6 +37,7 @@ public abstract class PodcastListFragment extends Fragment {
private ProgressBar progressBar;
private TextView txtvError;
private Button butRetry;
+ private Disposable disposable;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -64,60 +66,44 @@ public abstract class PodcastListFragment extends Fragment {
protected abstract List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException;
final void loadData() {
- AsyncTask<Void, Void, List<GpodnetPodcast>> loaderTask = new AsyncTask<Void, Void, List<GpodnetPodcast>>() {
- volatile Exception exception = null;
-
- @Override
- protected List<GpodnetPodcast> doInBackground(Void... params) {
- try {
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ gridView.setVisibility(View.GONE);
+ progressBar.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+ butRetry.setVisibility(View.GONE);
+ disposable = Observable.fromCallable(
+ () -> {
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl());
return loadPodcastData(service);
- } catch (GpodnetServiceException e) {
- exception = e;
- e.printStackTrace();
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(List<GpodnetPodcast> gpodnetPodcasts) {
- super.onPostExecute(gpodnetPodcasts);
- final Context context = getActivity();
- if (context != null && gpodnetPodcasts != null && gpodnetPodcasts.size() > 0) {
- PodcastListAdapter listAdapter = new PodcastListAdapter(context, 0, gpodnetPodcasts);
- gridView.setAdapter(listAdapter);
- listAdapter.notifyDataSetChanged();
-
- progressBar.setVisibility(View.GONE);
- gridView.setVisibility(View.VISIBLE);
- txtvError.setVisibility(View.GONE);
- butRetry.setVisibility(View.GONE);
- } else if (context != null && gpodnetPodcasts != null) {
- gridView.setVisibility(View.GONE);
- progressBar.setVisibility(View.GONE);
- txtvError.setText(getString(R.string.search_status_no_results));
- txtvError.setVisibility(View.VISIBLE);
- butRetry.setVisibility(View.GONE);
- } else if (context != null) {
- gridView.setVisibility(View.GONE);
- progressBar.setVisibility(View.GONE);
- txtvError.setText(getString(R.string.error_msg_prefix) + exception.getMessage());
- txtvError.setVisibility(View.VISIBLE);
- butRetry.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- gridView.setVisibility(View.GONE);
- progressBar.setVisibility(View.VISIBLE);
- txtvError.setVisibility(View.GONE);
- butRetry.setVisibility(View.GONE);
- }
- };
-
- loaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ podcasts -> {
+ progressBar.setVisibility(View.GONE);
+ butRetry.setVisibility(View.GONE);
+
+ if (podcasts.size() > 0) {
+ PodcastListAdapter listAdapter = new PodcastListAdapter(getContext(), 0, podcasts);
+ gridView.setAdapter(listAdapter);
+ listAdapter.notifyDataSetChanged();
+ gridView.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+ } else {
+ gridView.setVisibility(View.GONE);
+ txtvError.setText(getString(R.string.search_status_no_results));
+ txtvError.setVisibility(View.VISIBLE);
+ }
+ }, error -> {
+ gridView.setVisibility(View.GONE);
+ progressBar.setVisibility(View.GONE);
+ txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ butRetry.setVisibility(View.VISIBLE);
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
index a26ec9e84..9d0f99aa9 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
@@ -1,32 +1,34 @@
package de.danoeh.antennapod.fragment.gpodnet;
-import android.content.Context;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.fragment.app.ListFragment;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
-import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
-
-import java.util.List;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
public class TagListFragment extends ListFragment {
private static final int COUNT = 50;
+ private static final String TAG = "TagListFragment";
+ private Disposable disposable;
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setOnItemClickListener((parent, view1, position, id) -> {
GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position);
- MainActivity activity = (MainActivity) getActivity();
- activity.loadChildFragment(TagFragment.newInstance(tag));
+ ((MainActivity) getActivity()).loadChildFragment(TagFragment.newInstance(tag));
});
startLoadTask();
@@ -35,59 +37,36 @@ public class TagListFragment extends ListFragment {
@Override
public void onDestroyView() {
super.onDestroyView();
- cancelLoadTask();
- }
- private AsyncTask<Void, Void, List<GpodnetTag>> loadTask;
-
- private void cancelLoadTask() {
- if (loadTask != null && !loadTask.isCancelled()) {
- loadTask.cancel(true);
+ if (disposable != null) {
+ disposable.dispose();
}
}
private void startLoadTask() {
- cancelLoadTask();
- loadTask = new AsyncTask<Void, Void, List<GpodnetTag>>() {
- private Exception exception;
-
- @Override
- protected List<GpodnetTag> doInBackground(Void... params) {
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ setListShown(false);
+ disposable = Observable.fromCallable(
+ () -> {
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
GpodnetPreferences.getHosturl());
- try {
- return service.getTopTags(COUNT);
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- exception = e;
- return null;
- }
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- setListShown(false);
- }
-
- @Override
- protected void onPostExecute(List<GpodnetTag> gpodnetTags) {
- super.onPostExecute(gpodnetTags);
- final Context context = getActivity();
- if (context != null) {
- if (gpodnetTags != null) {
- setListAdapter(new TagListAdapter(context, android.R.layout.simple_list_item_1, gpodnetTags));
- } else if (exception != null) {
+ return service.getTopTags(COUNT);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ tags -> {
+ setListAdapter(new TagListAdapter(getContext(), android.R.layout.simple_list_item_1, tags));
+ setListShown(true);
+ }, error -> {
TextView txtvError = new TextView(getActivity());
- txtvError.setText(exception.getMessage());
+ txtvError.setText(error.getMessage());
getListView().setEmptyView(txtvError);
- }
- setListShown(true);
-
- }
- }
- };
- loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ setListShown(true);
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
}
}
diff --git a/app/src/main/res/layout/feeditemlist_item.xml b/app/src/main/res/layout/feeditemlist_item.xml
index a8ae5743e..e1f382e46 100644
--- a/app/src/main/res/layout/feeditemlist_item.xml
+++ b/app/src/main/res/layout/feeditemlist_item.xml
@@ -145,6 +145,7 @@
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:text="·"
+ android:importantForAccessibility="no"
tools:background="@android:color/holo_blue_light"/>
<TextView
@@ -163,6 +164,7 @@
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:text="·"
+ android:importantForAccessibility="no"
tools:background="@android:color/holo_blue_light"/>
<TextView
diff --git a/build.gradle b/build.gradle
index e3e66b1b6..aa7f8ebbe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -86,11 +86,6 @@ wrapper {
gradleVersion = "6.3"
}
-// free build hack: common functions
-def doFreeBuild() {
- return hasProperty("freeBuild")
-}
-
apply plugin: "checkstyle"
checkstyle {
toolVersion '8.24'
diff --git a/core/build.gradle b/core/build.gradle
index 29cef1cef..e68fa9f97 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -99,17 +99,12 @@ dependencies {
implementation 'com.google.android.exoplayer:exoplayer:2.11.8'
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
- // Add casting features
- // free build hack: skip some dependencies
- if (!doFreeBuild()) {
- playApi 'com.google.android.libraries.cast.companionlibrary:ccl:2.9.1'
- api 'androidx.mediarouter:mediarouter:1.0.0'
- playApi 'com.google.android.gms:play-services-cast:8.4.0'
- api "com.google.android.support:wearable:$wearableSupportVersion"
- compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
- } else {
- System.out.println("core: free build hack, skipping some dependencies")
- }
+ // Non-free dependencies:
+ playApi 'com.google.android.libraries.cast.companionlibrary:ccl:2.9.1'
+ playApi 'androidx.mediarouter:mediarouter:1.0.0'
+ playApi 'com.google.android.gms:play-services-cast:8.4.0'
+ playApi "com.google.android.support:wearable:$wearableSupportVersion"
+ compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
// bundle conscrypt with free builds
freeImplementation "org.conscrypt:conscrypt-android:$conscryptVersion"
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 da946cf0b..dd8a466eb 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
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.feed;
-import android.database.Cursor;
import android.text.TextUtils;
import androidx.annotation.Nullable;
@@ -9,11 +8,10 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.SortOrder;
/**
- * Data Object for a whole feed
+ * Data Object for a whole feed.
*
* @author daniel
*/
@@ -24,9 +22,14 @@ public class Feed extends FeedFile {
public static final String TYPE_ATOM1 = "atom";
public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:";
- /* title as defined by the feed */
+ /**
+ * title as defined by the feed.
+ */
private String feedTitle;
- /* custom title set by the user */
+
+ /**
+ * custom title set by the user.
+ */
private String customTitle;
/**
@@ -40,25 +43,25 @@ public class Feed extends FeedFile {
private String description;
private String language;
/**
- * Name of the author
+ * Name of the author.
*/
private String author;
private String imageUrl;
private List<FeedItem> items;
/**
- * String that identifies the last update (adopted from Last-Modified or ETag header)
+ * String that identifies the last update (adopted from Last-Modified or ETag header).
*/
private String lastUpdate;
private String paymentLink;
/**
- * Feed type, for example RSS 2 or Atom
+ * Feed type, for example RSS 2 or Atom.
*/
private String type;
/**
- * Feed preferences
+ * Feed preferences.
*/
private FeedPreferences preferences;
@@ -120,7 +123,7 @@ public class Feed extends FeedFile {
this.paged = paged;
this.nextPageLink = nextPageLink;
this.items = new ArrayList<>();
- if(filter != null) {
+ if (filter != null) {
this.itemfilter = new FeedItemFilter(filter);
} else {
this.itemfilter = new FeedItemFilter(new String[0]);
@@ -130,7 +133,7 @@ public class Feed extends FeedFile {
}
/**
- * This constructor is used for test purposes
+ * This constructor is used for test purposes.
*/
public Feed(long id, String lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
@@ -173,56 +176,6 @@ public class Feed extends FeedFile {
preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password);
}
- public static Feed fromCursor(Cursor cursor) {
- int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
- int indexLastUpdate = cursor.getColumnIndex(PodDBAdapter.KEY_LASTUPDATE);
- int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
- int indexCustomTitle = cursor.getColumnIndex(PodDBAdapter.KEY_CUSTOM_TITLE);
- int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK);
- int indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION);
- int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK);
- int indexAuthor = cursor.getColumnIndex(PodDBAdapter.KEY_AUTHOR);
- int indexLanguage = cursor.getColumnIndex(PodDBAdapter.KEY_LANGUAGE);
- int indexType = cursor.getColumnIndex(PodDBAdapter.KEY_TYPE);
- int indexFeedIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_IDENTIFIER);
- int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
- int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
- int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
- int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED);
- int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
- int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
- int indexSortOrder = cursor.getColumnIndex(PodDBAdapter.KEY_SORT_ORDER);
- int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
- int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
-
- Feed feed = new Feed(
- cursor.getLong(indexId),
- cursor.getString(indexLastUpdate),
- cursor.getString(indexTitle),
- cursor.getString(indexCustomTitle),
- cursor.getString(indexLink),
- cursor.getString(indexDescription),
- cursor.getString(indexPaymentLink),
- cursor.getString(indexAuthor),
- cursor.getString(indexLanguage),
- cursor.getString(indexType),
- cursor.getString(indexFeedIdentifier),
- cursor.getString(indexImageUrl),
- cursor.getString(indexFileUrl),
- cursor.getString(indexDownloadUrl),
- cursor.getInt(indexDownloaded) > 0,
- cursor.getInt(indexIsPaged) > 0,
- cursor.getString(indexNextPageLink),
- cursor.getString(indexHide),
- SortOrder.fromCodeString(cursor.getString(indexSortOrder)),
- cursor.getInt(indexLastUpdateFailed) > 0
- );
-
- FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
- feed.setPreferences(preferences);
- return feed;
- }
-
/**
* Returns the item at the specified index.
*
@@ -382,7 +335,7 @@ public class Feed extends FeedFile {
}
public void setCustomTitle(String customTitle) {
- if(customTitle == null || customTitle.equals(feedTitle)) {
+ if (customTitle == null || customTitle.equals(feedTitle)) {
this.customTitle = null;
} else {
this.customTitle = customTitle;
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 4a17fbbda..44b673a4d 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
@@ -172,8 +172,7 @@ public class DownloadService extends Service {
setupNotificationUpdaterIfNecessary();
syncExecutor.execute(() -> onDownloadQueued(intent));
} else if (numberOfDownloads.get() == 0) {
- stopForeground(true);
- stopSelf();
+ shutdown();
} else {
Log.d(TAG, "onStartCommand: Unknown intent");
}
@@ -227,10 +226,6 @@ public class DownloadService extends Service {
}
unregisterReceiver(cancelDownloadReceiver);
- stopForeground(true);
- NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(R.id.notification_downloading);
-
// if this was the initial gpodder sync, i.e. we just synced the feeds successfully,
// it is now time to sync the episode actions
SyncService.sync(this);
@@ -550,14 +545,7 @@ public class DownloadService extends Service {
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
Log.d(TAG, "Attempting shutdown");
- stopForeground(true);
- stopSelf();
-
- // Trick to hide the notification more quickly when the service is stopped
- // Without this, the second-last update of the notification stays for 3 seconds after onDestroy returns
- notificationUpdater.run();
- NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(R.id.notification_downloading);
+ shutdown();
}
}
@@ -647,4 +635,14 @@ public class DownloadService extends Service {
new PostDownloaderTask(downloads), 1, 1, TimeUnit.SECONDS);
}
}
+
+ private void shutdown() {
+ // If the service was run for a very short time, the system may delay closing
+ // the notification. Set the notification text now so that a misleading message
+ // is not left on the notification.
+ notificationUpdater.run();
+ cancelNotificationUpdater();
+ stopForeground(true);
+ stopSelf();
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
index 64ed85cf3..7c8fe9452 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
@@ -53,7 +53,7 @@ public class DownloadServiceNotification {
String contentTitle = context.getString(R.string.download_notification_title);
String downloadsLeft = (numDownloads > 0)
? context.getResources().getQuantityString(R.plurals.downloads_left, numDownloads, numDownloads)
- : context.getString(R.string.downloads_processing);
+ : context.getString(R.string.service_shutting_down);
String bigText = compileNotificationString(downloads);
notificationCompatBuilder.setContentTitle(contentTitle);
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 27f08243f..fcf61b070 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
@@ -23,6 +23,7 @@ import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.storage.mapper.FeedCursorMapper;
import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
@@ -204,7 +205,7 @@ public final class DBReader {
}
private static Feed extractFeedFromCursorRow(Cursor cursor) {
- Feed feed = Feed.fromCursor(cursor);
+ Feed feed = FeedCursorMapper.convert(cursor);
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
return feed;
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 f5a8fb07a..596ab624e 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
@@ -19,6 +19,7 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.storage.mapper.FeedCursorMapper;
import de.danoeh.antennapod.core.sync.SyncService;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.LongList;
@@ -516,7 +517,7 @@ public final class DBTasks {
List<Feed> items = new ArrayList<>();
if (cursor.moveToFirst()) {
do {
- items.add(Feed.fromCursor(cursor));
+ items.add(FeedCursorMapper.convert(cursor));
} while (cursor.moveToNext());
}
setResult(items);
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 8d1352a10..adb5e6a74 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
@@ -15,6 +15,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import de.danoeh.antennapod.core.storage.mapper.FeedItemFilterQuery;
import org.apache.commons.io.FileUtils;
@@ -372,6 +373,7 @@ public class PodDBAdapter {
* For more information see
* <a href="https://github.com/robolectric/robolectric/issues/1890">robolectric/robolectric#1890</a>.</p>
*/
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
public static void tearDownTests() {
getInstance().dbHelper.close();
instance = null;
@@ -1379,7 +1381,16 @@ public class PodDBAdapter {
}
/**
- * Called when a database corruption happens
+ * Insert raw data to the database. *
+ * Call method only for unit tests.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public void insertTestData(@NonNull String table, @NonNull ContentValues values) {
+ db.insert(table, null, values);
+ }
+
+ /**
+ * Called when a database corruption happens.
*/
public static class PodDbErrorHandler implements DatabaseErrorHandler {
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java
new file mode 100644
index 000000000..783fba596
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java
@@ -0,0 +1,70 @@
+package de.danoeh.antennapod.core.storage.mapper;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.core.util.SortOrder;
+
+/**
+ * Converts a {@link Cursor} to a {@link Feed} object.
+ */
+public abstract class FeedCursorMapper {
+
+ /**
+ * Create a {@link Feed} instance from a database row (cursor).
+ */
+ @NonNull
+ public static Feed convert(@NonNull Cursor cursor) {
+ int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
+ int indexLastUpdate = cursor.getColumnIndex(PodDBAdapter.KEY_LASTUPDATE);
+ int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
+ int indexCustomTitle = cursor.getColumnIndex(PodDBAdapter.KEY_CUSTOM_TITLE);
+ int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK);
+ int indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION);
+ int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK);
+ int indexAuthor = cursor.getColumnIndex(PodDBAdapter.KEY_AUTHOR);
+ int indexLanguage = cursor.getColumnIndex(PodDBAdapter.KEY_LANGUAGE);
+ int indexType = cursor.getColumnIndex(PodDBAdapter.KEY_TYPE);
+ int indexFeedIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_IDENTIFIER);
+ int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
+ int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
+ int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
+ int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED);
+ int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
+ int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
+ int indexSortOrder = cursor.getColumnIndex(PodDBAdapter.KEY_SORT_ORDER);
+ int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
+ int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
+
+ Feed feed = new Feed(
+ cursor.getLong(indexId),
+ cursor.getString(indexLastUpdate),
+ cursor.getString(indexTitle),
+ cursor.getString(indexCustomTitle),
+ cursor.getString(indexLink),
+ cursor.getString(indexDescription),
+ cursor.getString(indexPaymentLink),
+ cursor.getString(indexAuthor),
+ cursor.getString(indexLanguage),
+ cursor.getString(indexType),
+ cursor.getString(indexFeedIdentifier),
+ cursor.getString(indexImageUrl),
+ cursor.getString(indexFileUrl),
+ cursor.getString(indexDownloadUrl),
+ cursor.getInt(indexDownloaded) > 0,
+ cursor.getInt(indexIsPaged) > 0,
+ cursor.getString(indexNextPageLink),
+ cursor.getString(indexHide),
+ SortOrder.fromCodeString(cursor.getString(indexSortOrder)),
+ cursor.getInt(indexLastUpdateFailed) > 0
+ );
+
+ FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
+ feed.setPreferences(preferences);
+ return feed;
+ }
+}
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index e9c1d8fcd..8e15d60d7 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -270,7 +270,7 @@
<item quantity="one">%d download left</item>
<item quantity="other">%d downloads left</item>
</plurals>
- <string name="downloads_processing">Processing downloads</string>
+ <string name="service_shutting_down">Service shutting down</string>
<string name="download_notification_title">Downloading podcast data</string>
<plurals name="download_report_content">
<item quantity="one">%d download succeeded, %d failed</item>
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java
new file mode 100644
index 000000000..c779b6d55
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java
@@ -0,0 +1,100 @@
+package de.danoeh.antennapod.core.storage.mapper;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(RobolectricTestRunner.class)
+public class FeedCursorMapperTest {
+ private PodDBAdapter adapter;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+ PodDBAdapter.init(context);
+ adapter = PodDBAdapter.getInstance();
+
+ writeFeedToDatabase();
+ }
+
+ @After
+ public void tearDown() {
+ PodDBAdapter.tearDownTests();
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ public void testFromCursor() {
+ try (Cursor cursor = adapter.getAllFeedsCursor()) {
+ cursor.moveToNext();
+ Feed feed = FeedCursorMapper.convert(cursor);
+ assertTrue(feed.getId() >= 0);
+ assertEquals("feed custom title", feed.getTitle());
+ assertEquals("feed custom title", feed.getCustomTitle());
+ assertEquals("feed link", feed.getLink());
+ assertEquals("feed description", feed.getDescription());
+ assertEquals("feed payment link", feed.getPaymentLink());
+ assertEquals("feed author", feed.getAuthor());
+ assertEquals("feed language", feed.getLanguage());
+ assertEquals("feed image url", feed.getImageUrl());
+ assertEquals("feed file url", feed.getFile_url());
+ assertEquals("feed download url", feed.getDownload_url());
+ assertTrue(feed.isDownloaded());
+ assertEquals("feed last update", feed.getLastUpdate());
+ assertEquals("feed type", feed.getType());
+ assertEquals("feed identifier", feed.getFeedIdentifier());
+ assertTrue(feed.isPaged());
+ assertEquals("feed next page link", feed.getNextPageLink());
+ assertTrue(feed.getItemFilter().showUnplayed);
+ assertEquals(1, feed.getSortOrder().code);
+ assertTrue(feed.hasLastUpdateFailed());
+ }
+ }
+
+ /**
+ * Insert test data to the database.
+ * Uses raw database insert instead of adapter.setCompleteFeed() to avoid testing the Feed class
+ * against itself.
+ */
+ private void writeFeedToDatabase() {
+ ContentValues values = new ContentValues();
+ values.put(PodDBAdapter.KEY_TITLE, "feed title");
+ values.put(PodDBAdapter.KEY_CUSTOM_TITLE, "feed custom title");
+ values.put(PodDBAdapter.KEY_LINK, "feed link");
+ values.put(PodDBAdapter.KEY_DESCRIPTION, "feed description");
+ values.put(PodDBAdapter.KEY_PAYMENT_LINK, "feed payment link");
+ values.put(PodDBAdapter.KEY_AUTHOR, "feed author");
+ values.put(PodDBAdapter.KEY_LANGUAGE, "feed language");
+ values.put(PodDBAdapter.KEY_IMAGE_URL, "feed image url");
+
+ values.put(PodDBAdapter.KEY_FILE_URL, "feed file url");
+ values.put(PodDBAdapter.KEY_DOWNLOAD_URL, "feed download url");
+ values.put(PodDBAdapter.KEY_DOWNLOADED, true);
+ values.put(PodDBAdapter.KEY_LASTUPDATE, "feed last update");
+ values.put(PodDBAdapter.KEY_TYPE, "feed type");
+ values.put(PodDBAdapter.KEY_FEED_IDENTIFIER, "feed identifier");
+
+ values.put(PodDBAdapter.KEY_IS_PAGED, true);
+ values.put(PodDBAdapter.KEY_NEXT_PAGE_LINK, "feed next page link");
+ values.put(PodDBAdapter.KEY_HIDE, "unplayed");
+ values.put(PodDBAdapter.KEY_SORT_ORDER, "1");
+ values.put(PodDBAdapter.KEY_LAST_UPDATE_FAILED, true);
+
+ adapter.insertTestData(PodDBAdapter.TABLE_NAME_FEEDS, values);
+ }
+}