Move feed download to worker (#6375)

Feed downloads are now independent from episode downloads.
This makes it easier to use WorkManager for refreshing.
Also, it will make it easier to add different refresh intervals
in the future.
This commit is contained in:
ByteHamster 2023-03-14 21:03:45 +01:00 committed by GitHub
parent 2c0b970044
commit 4f7f49e1e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 533 additions and 972 deletions

View File

@ -2,13 +2,11 @@ package de.test.antennapod.ui;
import android.content.Intent;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.test.antennapod.EspressoTestUtils;
import org.junit.After;
import org.junit.Before;
@ -18,8 +16,6 @@ import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.pressBack;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@ -28,10 +24,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.clickPreference;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
public class FeedSettingsTest {
@ -84,52 +76,4 @@ public class FeedSettingsTest {
clickPreference(R.string.feed_volume_reduction);
onView(withText(R.string.cancel_label)).perform(click());
}
/**
* Test that modifying a feed's authentication settings results in proper behavior.
* Expect:
* - Feed is refreshed automatically
* - Database has updated username and password
*/
@Test
public void testAuthenticationSettingsUpdate() throws IOException {
onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.appBar)),
withText(feed.getTitle()), isDisplayed()), 1000));
String updatedTitle = "modified episode title";
String username = "username";
String password = "password";
// update feed hosted on server
feed.getItems().get(0).setTitle(updatedTitle);
uiTestUtils.hostFeed(feed);
// interact with UI to update authentication settings
updateAuthenticationSettings(username, password);
// expect feed to have refreshed and be showing new episode title
onView(isRoot()).perform(waitForView(withText(updatedTitle), 5000));
// expect database to be updated with correct username and password
Feed updatedFeed = DBReader.getFeed(feed.getId());
assertNotNull(updatedFeed);
FeedPreferences updatedFeedPreferences = updatedFeed.getPreferences();
assertNotNull(updatedFeedPreferences);
assertEquals("database updated with username", username, updatedFeedPreferences.getUsername());
assertEquals("database updated with password", password, updatedFeedPreferences.getPassword());
}
private void updateAuthenticationSettings(String username, String password) {
onView(withId(R.id.butShowSettings)).perform(click());
clickPreference(R.string.authentication_label);
onView(withId(R.id.usernameEditText)).perform(typeText(username));
onView(withId(R.id.passwordEditText)).perform(typeText(password));
onView(withText(R.string.confirm_label)).perform(click());
onView(isRoot()).perform(pressBack());
}
}

View File

@ -5,7 +5,6 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
import androidx.test.espresso.matcher.RootMatchers;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import de.danoeh.antennapod.R;
@ -25,9 +24,7 @@ import org.junit.Rule;
import org.junit.Test;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
@ -46,7 +43,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.clickPreference;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertTrue;
@ -226,30 +222,6 @@ public class PreferencesTest {
.until(() -> pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss());
}
@Test
public void testDisableUpdateInterval() {
clickPreference(R.string.network_pref);
clickPreference(R.string.feed_refresh_title);
onView(withText(R.string.feed_refresh_never)).perform(click());
onView(withId(R.id.disableRadioButton)).perform(click());
onView(withText(R.string.confirm_label)).perform(click());
Awaitility.await().atMost(1000, MILLISECONDS)
.until(() -> UserPreferences.getUpdateInterval() == 0);
}
@Test
public void testSetUpdateInterval() {
clickPreference(R.string.network_pref);
clickPreference(R.string.feed_refresh_title);
onView(withId(R.id.intervalRadioButton)).perform(click());
onView(withId(R.id.spinner)).perform(click());
int position = 1; // an arbitrary position
onData(anything()).inRoot(RootMatchers.isPlatformPopup()).atPosition(position).perform(click());
onView(withText(R.string.confirm_label)).perform(click());
Awaitility.await().atMost(1000, MILLISECONDS)
.until(() -> UserPreferences.getUpdateInterval() == TimeUnit.HOURS.toMillis(2));
}
@Test
public void testSetSequentialDownload() {
clickPreference(R.string.network_pref);

View File

@ -22,36 +22,31 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.appbar.MaterialToolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.bumptech.glide.Glide;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.core.preferences.ThemeSwitcher;
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.preferences.ThemeSwitcher;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.dialog.RatingDialog;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.fragment.AddFeedFragment;
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
import de.danoeh.antennapod.fragment.AudioPlayerFragment;
import de.danoeh.antennapod.fragment.CompletedDownloadsFragment;
import de.danoeh.antennapod.fragment.InboxFragment;
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
import de.danoeh.antennapod.fragment.InboxFragment;
import de.danoeh.antennapod.fragment.NavDrawerFragment;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
@ -60,10 +55,16 @@ import de.danoeh.antennapod.fragment.SubscriptionFragment;
import de.danoeh.antennapod.fragment.TransitionEffect;
import de.danoeh.antennapod.playback.cast.CastEnabledActivity;
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.home.HomeFragment;
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
/**
* The activity that is shown when the user launches the app.
@ -157,6 +158,21 @@ public class MainActivity extends CastEnabledActivity {
sheetBehavior = (LockableBottomSheetBehavior) BottomSheetBehavior.from(bottomSheet);
sheetBehavior.setHideable(false);
sheetBehavior.setBottomSheetCallback(bottomSheetCallback);
FeedUpdateManager.restartUpdateAlarm(this, false);
WorkManager.getInstance(this)
.getWorkInfosByTagLiveData(FeedUpdateManager.WORK_TAG_FEED_UPDATE)
.observe(this, workInfos -> {
boolean isRefreshingFeeds = false;
for (WorkInfo workInfo : workInfos) {
if (workInfo.getState() == WorkInfo.State.RUNNING) {
isRefreshingFeeds = true;
} else if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
isRefreshingFeeds = true;
}
}
EventBus.getDefault().postSticky(new FeedUpdateRunningEvent(isRefreshingFeeds));
});
}
@Override
@ -241,9 +257,7 @@ public class MainActivity extends CastEnabledActivity {
private void checkFirstLaunch() {
SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
// for backward compatibility, we only change defaults for fresh installs
UserPreferences.setUpdateInterval(12);
AutoUpdateManager.restartUpdateAlarm(this);
FeedUpdateManager.restartUpdateAlarm(this, true);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
@ -553,7 +567,7 @@ public class MainActivity extends CastEnabledActivity {
drawerLayout.open();
}
if (intent.getBooleanExtra(EXTRA_REFRESH_ON_START, false)) {
AutoUpdateManager.runImmediate(this);
FeedUpdateManager.runOnceOrAsk(this);
}
// to avoid handling the intent twice when the configuration changes
setIntent(new Intent(MainActivity.this, MainActivity.class));

View File

@ -36,7 +36,7 @@ import de.danoeh.antennapod.core.preferences.ThemeSwitcher;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceInterface;
import de.danoeh.antennapod.core.util.DownloadErrorLabel;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
@ -455,10 +455,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
if (feedInFeedlist()) {
openFeed();
} else {
Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle());
DownloadServiceInterface.get().download(this, false, DownloadRequestCreator.create(f)
.withAuthentication(username, password)
.build());
DBTasks.updateFeed(this, feed, false);
didPressSubscribe = true;
handleUpdatedFeedStatus();
}

View File

@ -29,7 +29,7 @@ import de.danoeh.antennapod.core.export.opml.OpmlReader;
import de.danoeh.antennapod.core.preferences.ThemeSwitcher;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.databinding.OpmlSelectionBinding;
import de.danoeh.antennapod.model.feed.Feed;
import io.reactivex.Completable;
@ -101,7 +101,7 @@ public class OpmlImportActivity extends AppCompatActivity {
feed.setItems(Collections.emptyList());
DBTasks.updateFeed(this, feed, false);
}
DownloadServiceInterface.get().refreshAllFeeds(this, true);
FeedUpdateManager.runOnce(this);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View File

@ -1,117 +0,0 @@
package de.danoeh.antennapod.dialog;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.databinding.FeedRefreshDialogBinding;
import de.danoeh.antennapod.databinding.ScrollableDialogBinding;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.ArrayUtils;
public class FeedRefreshIntervalDialog {
private static final int[] INTERVAL_VALUES_HOURS = {1, 2, 4, 8, 12, 24, 72};
private final Context context;
private FeedRefreshDialogBinding viewBinding;
public FeedRefreshIntervalDialog(Context context) {
this.context = context;
}
public void show() {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.feed_refresh_title);
builder.setMessage(R.string.feed_refresh_sum);
ScrollableDialogBinding scrollableDialogBinding = ScrollableDialogBinding.inflate(LayoutInflater.from(context));
builder.setView(scrollableDialogBinding.getRoot());
viewBinding = FeedRefreshDialogBinding.inflate(LayoutInflater.from(context));
scrollableDialogBinding.content.addView(viewBinding.getRoot());
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(context,
android.R.layout.simple_spinner_item, buildSpinnerEntries());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
viewBinding.spinner.setAdapter(spinnerArrayAdapter);
viewBinding.timePicker.setIs24HourView(DateFormat.is24HourFormat(context));
viewBinding.spinner.setSelection(ArrayUtils.indexOf(INTERVAL_VALUES_HOURS, 24));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
viewBinding.timePicker.setHour(8);
viewBinding.timePicker.setMinute(0);
} else {
viewBinding.timePicker.setCurrentHour(8);
viewBinding.timePicker.setCurrentMinute(0);
}
long currInterval = UserPreferences.getUpdateInterval();
int[] updateTime = UserPreferences.getUpdateTimeOfDay();
if (currInterval > 0) {
viewBinding.spinner.setSelection(ArrayUtils.indexOf(INTERVAL_VALUES_HOURS,
(int) TimeUnit.MILLISECONDS.toHours(currInterval)));
viewBinding.intervalRadioButton.setChecked(true);
} else if (updateTime.length == 2 && updateTime[0] >= 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
viewBinding.timePicker.setHour(updateTime[0]);
viewBinding.timePicker.setMinute(updateTime[1]);
} else {
viewBinding.timePicker.setCurrentHour(updateTime[0]);
viewBinding.timePicker.setCurrentMinute(updateTime[1]);
}
viewBinding.timeRadioButton.setChecked(true);
} else {
viewBinding.disableRadioButton.setChecked(true);
}
updateVisibility();
viewBinding.radioGroup.setOnCheckedChangeListener((radioGroup, i) -> updateVisibility());
AlertDialog dialog = builder.show();
scrollableDialogBinding.positiveButton.setText(R.string.confirm_label);
scrollableDialogBinding.positiveButton.setOnClickListener(v -> {
dialog.dismiss();
if (viewBinding.intervalRadioButton.isChecked()) {
UserPreferences.setUpdateInterval(INTERVAL_VALUES_HOURS[viewBinding.spinner.getSelectedItemPosition()]);
AutoUpdateManager.restartUpdateAlarm(context);
} else if (viewBinding.timeRadioButton.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
UserPreferences.setUpdateTimeOfDay(viewBinding.timePicker.getHour(),
viewBinding.timePicker.getMinute());
} else {
UserPreferences.setUpdateTimeOfDay(viewBinding.timePicker.getCurrentHour(),
viewBinding.timePicker.getCurrentMinute());
}
AutoUpdateManager.restartUpdateAlarm(context);
} else if (viewBinding.disableRadioButton.isChecked()) {
UserPreferences.disableAutoUpdate();
AutoUpdateManager.disableAutoUpdate(context);
} else {
throw new IllegalStateException("Unexpected error.");
}
});
scrollableDialogBinding.negativeButton.setText(R.string.cancel_label);
scrollableDialogBinding.negativeButton.setOnClickListener((v) -> dialog.dismiss());
}
private String[] buildSpinnerEntries() {
final Resources res = context.getResources();
String[] entries = new String[INTERVAL_VALUES_HOURS.length];
for (int i = 0; i < INTERVAL_VALUES_HOURS.length; i++) {
int hours = INTERVAL_VALUES_HOURS[i];
entries[i] = res.getQuantityString(R.plurals.feed_refresh_every_x_hours, hours, hours);
}
return entries;
}
private void updateVisibility() {
viewBinding.spinner.setVisibility(viewBinding.intervalRadioButton.isChecked() ? View.VISIBLE : View.GONE);
viewBinding.timePicker.setVisibility(viewBinding.timeRadioButton.isChecked() ? View.VISIBLE : View.GONE);
}
}

View File

@ -25,7 +25,7 @@ import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
@ -193,7 +193,7 @@ public class CompletedDownloadsFragment extends Fragment
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
return true;
} else if (item.getItemId() == R.id.action_download_logs) {
new DownloadLogFragment().show(getChildFragmentManager(), null);

View File

@ -30,11 +30,11 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
@ -123,7 +123,7 @@ public abstract class EpisodesListFragment extends Fragment
}
final int itemId = item.getItemId();
if (itemId == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
return true;
} else if (itemId == R.id.action_search) {
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance());
@ -184,7 +184,7 @@ public abstract class EpisodesListFragment extends Fragment
SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh);
swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance));
swipeRefreshLayout.setOnRefreshListener(() -> {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false),
getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms));
});
@ -455,9 +455,12 @@ public abstract class EpisodesListFragment extends Fragment
protected abstract String getPrefName();
protected void updateToolbar() {
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedUpdateRunningEvent event) {
if (toolbar.getMenu().findItem(R.id.refresh_item) != null) {
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item,
DownloadService.isRunning && DownloadService.isDownloadingFeeds());
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning);
}
}

View File

@ -33,11 +33,11 @@ import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
import de.danoeh.antennapod.databinding.FeedItemListFragmentBinding;
import de.danoeh.antennapod.databinding.MultiSelectSpeedDialBinding;
@ -48,6 +48,7 @@ import de.danoeh.antennapod.dialog.RenameItemDialog;
import de.danoeh.antennapod.event.FavoritesEvent;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.QueueEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
@ -164,7 +165,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
nextPageLoader = new MoreContentListFooterUtil(viewBinding.moreContent.moreContentListFooter);
nextPageLoader.setClickListener(() -> {
if (feed != null) {
DBTasks.loadNextPageOfFeed(getActivity(), feed, false);
FeedUpdateManager.runOnce(getContext(), feed, true);
}
});
viewBinding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@ -241,8 +242,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
}
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed.getLink() != null);
MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(), R.id.refresh_item,
DownloadService.isRunning && DownloadService.isDownloadingFile(feed.getDownload_url()));
FeedMenuHandler.onPrepareOptionsMenu(viewBinding.toolbar.getMenu(), feed);
}
@ -384,7 +383,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
private void updateUi() {
loadItems();
updateSyncProgressBarVisibility();
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -404,12 +402,14 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
}
}
private void updateSyncProgressBarVisibility() {
updateToolbar();
if (!DownloadService.isDownloadingFeeds()) {
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedUpdateRunningEvent event) {
nextPageLoader.setLoadingState(event.isFeedUpdateRunning);
if (!event.isFeedUpdateRunning) {
nextPageLoader.getRoot().setVisibility(View.GONE);
}
nextPageLoader.setLoadingState(DownloadService.isDownloadingFeeds());
MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(),
R.id.refresh_item, event.isFeedUpdateRunning);
}
private void refreshHeaderView() {
@ -534,14 +534,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
adapter.setDummyViews(0);
adapter.updateItems(feed.getItems());
updateToolbar();
updateSyncProgressBarVisibility();
}, error -> {
feed = null;
refreshHeaderView();
adapter.setDummyViews(0);
adapter.updateItems(Collections.emptyList());
updateToolbar();
updateSyncProgressBarVisibility();
Log.e(TAG, Log.getStackTraceString(error));
});
}

View File

@ -16,7 +16,7 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
import androidx.recyclerview.widget.RecyclerView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.settings.SkipIntroEndingChangedEvent;
import de.danoeh.antennapod.event.settings.SpeedPresetChangedEvent;
import de.danoeh.antennapod.event.settings.VolumeAdaptionChangedEvent;
@ -270,8 +270,7 @@ public class FeedSettingsFragment extends Fragment {
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
DBTasks.forceRefreshFeed(requireContext(), feed, true);
FeedUpdateManager.runOnce(getContext(), feed);
}, "RefreshAfterCredentialChange").start();
}
}.show();

View File

@ -18,13 +18,13 @@ import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.appbar.MaterialToolbar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.R;
@ -36,14 +36,13 @@ import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.QueueEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
@ -54,6 +53,7 @@ import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.LiftOnScrollListener;
@ -263,8 +263,11 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
toolbar.getMenu().findItem(R.id.queue_lock).setVisible(!keepSorted);
toolbar.getMenu().findItem(R.id.sort_random).setVisible(!keepSorted);
toolbar.getMenu().findItem(R.id.keep_sorted).setChecked(keepSorted);
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(),
R.id.refresh_item, DownloadService.isRunning && DownloadService.isDownloadingFeeds());
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedUpdateRunningEvent event) {
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning);
}
@Override
@ -274,7 +277,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
toggleQueueLock();
return true;
} else if (itemId == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
return true;
} else if (itemId == R.id.clear_queue) {
// make sure the user really wants to clear the queue
@ -457,7 +460,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh);
swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance));
swipeRefreshLayout.setOnRefreshListener(() -> {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false),
getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms));
});

View File

@ -13,20 +13,40 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.google.android.material.appbar.MaterialToolbar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.joanzapata.iconify.Iconify;
import com.leinardi.android.speeddial.SpeedDialView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.SubscriptionsRecyclerAdapter;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.NavDrawerData;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.dialog.FeedSortDialog;
import de.danoeh.antennapod.dialog.RenameItemDialog;
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.fragment.actions.FeedMultiSelectActionHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.statistics.StatisticsFragment;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.LiftOnScrollListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@ -35,30 +55,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.SubscriptionsRecyclerAdapter;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.NavDrawerData;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.dialog.FeedSortDialog;
import de.danoeh.antennapod.dialog.RenameItemDialog;
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
import de.danoeh.antennapod.fragment.actions.FeedMultiSelectActionHandler;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
* Fragment for displaying feed subscriptions
*/
@ -173,7 +169,7 @@ public class SubscriptionFragment extends Fragment
SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh);
swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance));
swipeRefreshLayout.setOnRefreshListener(() -> {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
new Handler(Looper.getMainLooper()).postDelayed(() -> swipeRefreshLayout.setRefreshing(false),
getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms));
});
@ -209,16 +205,18 @@ public class SubscriptionFragment extends Fragment
private void refreshToolbarState() {
int columns = prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns());
toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS]).setChecked(true);
}
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item,
DownloadService.isRunning && DownloadService.isDownloadingFeeds());
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedUpdateRunningEvent event) {
MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), R.id.refresh_item, event.isFeedUpdateRunning);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
return true;
} else if (itemId == R.id.subscriptions_filter) {
SubscriptionsFilterDialog.showDialog(requireContext());

View File

@ -1,21 +1,15 @@
package de.danoeh.antennapod.fragment.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.format.DateFormat;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.dialog.FeedRefreshIntervalDialog;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.dialog.ProxyDialog;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
public class NetworkPreferencesFragment extends PreferenceFragmentCompat
@ -45,7 +39,6 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat
@Override
public void onResume() {
super.onResume();
setUpdateIntervalText();
setParallelDownloadsText(UserPreferences.getParallelDownloads());
}
@ -54,21 +47,12 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_autodownload);
return true;
});
findPreference(UserPreferences.PREF_UPDATE_INTERVAL)
.setOnPreferenceClickListener(preference -> {
new FeedRefreshIntervalDialog(getContext()).show();
return true;
});
findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)
.setOnPreferenceChangeListener(
(preference, o) -> {
if (o instanceof Integer) {
setParallelDownloadsText((Integer) o);
}
return true;
}
);
findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setOnPreferenceChangeListener((preference, o) -> {
if (o instanceof Integer) {
setParallelDownloadsText((Integer) o);
}
return true;
});
// validate and set correct value: number of downloads between 1 and 50 (inclusive)
findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> {
ProxyDialog dialog = new ProxyDialog(getActivity());
@ -77,35 +61,6 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat
});
}
/**
* Used to init and handle changes to view
*/
private void setUpdateIntervalText() {
Context context = getActivity().getApplicationContext();
String val;
long interval = UserPreferences.getUpdateInterval();
if (interval > 0) {
int hours = (int) TimeUnit.MILLISECONDS.toHours(interval);
val = context.getResources().getQuantityString(
R.plurals.feed_refresh_every_x_hours, hours, hours);
} else {
int[] timeOfDay = UserPreferences.getUpdateTimeOfDay();
if (timeOfDay.length == 2) {
Calendar cal = new GregorianCalendar();
cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]);
cal.set(Calendar.MINUTE, timeOfDay[1]);
String timeOfDayStr = DateFormat.getTimeFormat(context).format(cal.getTime());
val = String.format(context.getString(R.string.feed_refresh_interval_at),
timeOfDayStr);
} else {
val = context.getString(R.string.feed_refresh_never);
}
}
String summary = context.getString(R.string.feed_refresh_sum) + "\n"
+ String.format(context.getString(R.string.pref_current_value), val);
findPreference(UserPreferences.PREF_UPDATE_INTERVAL).setSummary(summary);
}
private void setParallelDownloadsText(int downloads) {
final Resources res = getActivity().getResources();
String s = res.getString(R.string.parallel_downloads, downloads);
@ -115,9 +70,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (UserPreferences.PREF_UPDATE_INTERVAL.equals(key)) {
setUpdateIntervalText();
FeedUpdateManager.restartUpdateAlarm(getContext(), true);
}
}
}

View File

@ -10,6 +10,7 @@ import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ShareUtils;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.dialog.IntraFeedSortDialog;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.SortOrder;
@ -26,6 +27,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
@ -64,7 +66,16 @@ public class FeedMenuHandler {
if (itemId == R.id.refresh_item) {
DBTasks.forceRefreshFeed(context, selectedFeed, true);
} else if (itemId == R.id.refresh_complete_item) {
DBTasks.forceRefreshCompleteFeed(context, selectedFeed);
new Thread(() -> {
selectedFeed.setNextPageLink(selectedFeed.getDownload_url());
selectedFeed.setPageNr(0);
try {
DBWriter.resetPagedFeedPage(selectedFeed).get();
FeedUpdateManager.runOnce(context, selectedFeed);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
} else if (itemId == R.id.sort_items) {
showSortDialog(context, selectedFeed);
} else if (itemId == R.id.visit_website_item) {

View File

@ -13,7 +13,7 @@ import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.error.CrashReportWriter;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.fragment.swipeactions.SwipeAction;
import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
@ -31,7 +31,7 @@ public class PreferenceUpgrader {
int newVersion = BuildConfig.VERSION_CODE;
if (oldVersion != newVersion) {
AutoUpdateManager.restartUpdateAlarm(context);
FeedUpdateManager.restartUpdateAlarm(context, true);
CrashReportWriter.getFile().delete();
upgrade(oldVersion, context);
@ -138,6 +138,9 @@ public class PreferenceUpgrader {
.apply();
}
UserPreferences.setAllowMobileSync(true);
if (prefs.getString(UserPreferences.PREF_UPDATE_INTERVAL, ":").contains(":")) { // Unset or "time of day"
prefs.edit().putString(UserPreferences.PREF_UPDATE_INTERVAL, "12").apply();
}
}
}
}

View File

@ -8,11 +8,12 @@ import android.util.Log;
import android.widget.Toast;
import java.util.Arrays;
import java.util.Collections;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.ClientConfigurator;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.model.feed.Feed;
/**
@ -43,9 +44,11 @@ public class SPAReceiver extends BroadcastReceiver{
Log.d(TAG, "Received feeds list: " + Arrays.toString(feedUrls));
ClientConfigurator.initialize(context);
for (String url : feedUrls) {
Feed f = new Feed(url, null);
DownloadServiceInterface.get().download(context, false, DownloadRequestCreator.create(f).build());
Feed feed = new Feed(url, null, "Unknown podcast");
feed.setItems(Collections.emptyList());
DBTasks.updateFeed(context, feed, false);
}
Toast.makeText(context, R.string.sp_apps_importing_feeds_msg, Toast.LENGTH_LONG).show();
FeedUpdateManager.runOnce(context);
}
}

View File

@ -17,13 +17,12 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.databinding.HomeFragmentBinding;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.fragment.SearchFragment;
import de.danoeh.antennapod.ui.home.sections.DownloadsSection;
import de.danoeh.antennapod.ui.home.sections.EpisodesSurpriseSection;
@ -69,13 +68,12 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
}
viewBinding.homeScrollView.setOnScrollChangeListener(new LiftOnScrollListener(viewBinding.appbar));
((MainActivity) requireActivity()).setupToolbarToggle(viewBinding.toolbar, displayUpArrow);
refreshToolbarState();
populateSectionList();
updateWelcomeScreenVisibility();
viewBinding.swipeRefresh.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance));
viewBinding.swipeRefresh.setOnRefreshListener(() -> {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
new Handler(Looper.getMainLooper()).postDelayed(() -> viewBinding.swipeRefresh.setRefreshing(false),
getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms));
});
@ -126,14 +124,10 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenSectionsString, ",")));
}
private void refreshToolbarState() {
MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(),
R.id.refresh_item, DownloadService.isRunning && DownloadService.isDownloadingFeeds());
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(DownloadEvent event) {
refreshToolbarState();
public void onEventMainThread(FeedUpdateRunningEvent event) {
MenuItemUtils.updateRefreshMenuItem(viewBinding.toolbar.getMenu(),
R.id.refresh_item, event.isFeedUpdateRunning);
}
@Override
@ -142,7 +136,7 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
HomeSectionsSettingsDialog.open(getContext(), (dialogInterface, i) -> populateSectionList());
return true;
} else if (item.getItemId() == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
FeedUpdateManager.runOnceOrAsk(requireContext());
return true;
} else if (item.getItemId() == R.id.action_search) {
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance());

View File

@ -1,31 +0,0 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.util.AttributeSet;
/**
* Samsung's Android 6.0.1 has a bug that crashes the app when inflating a time picker.
* This class serves as a workaround for affected devices.
*/
public class TimePicker extends android.widget.TimePicker {
public TimePicker(Context context) {
super(context);
}
public TimePicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
try {
super.onRtlPropertiesChanged(layoutDirection);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RadioGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<RadioButton
android:id="@+id/intervalRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/feed_refresh_interval" />
<Spinner
android:id="@+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<RadioButton
android:id="@+id/timeRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/feed_refresh_time" />
<de.danoeh.antennapod.view.TimePicker
android:id="@+id/timePicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:timePickerMode="spinner"
android:visibility="gone" />
<RadioButton
android:id="@+id/disableRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/feed_refresh_never" />
</RadioGroup>

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal"
android:paddingHorizontal="32dp"
android:paddingVertical="16dp"
style="?android:attr/buttonBarStyle">
<Button
android:id="@+id/negativeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/positiveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
</LinearLayout>

View File

@ -4,10 +4,13 @@
xmlns:numberpicker="http://schemas.android.com/apk/de.danoeh.antennapod"
xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch">
<PreferenceCategory android:title="@string/automation">
<Preference
<de.danoeh.antennapod.preferences.MaterialListPreference
android:entryValues="@array/feed_refresh_interval_values"
android:entries="@array/feed_refresh_interval_entries"
android:key="prefAutoUpdateIntervall"
android:title="@string/feed_refresh_title"
android:summary="@string/feed_refresh_sum"
android:title="@string/feed_refresh_title"/>
android:defaultValue="12"/>
<Preference
android:summary="@string/pref_automatic_download_sum"
android:key="prefAutoDownloadSettings"

View File

@ -62,6 +62,7 @@ dependencies {
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbusVersion"
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
implementation 'com.annimon:stream:1.2.2'
implementation "com.google.android.exoplayer:exoplayer-core:$exoPlayerVersion"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion"

View File

@ -8,9 +8,8 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlPullParserException;
@ -30,6 +29,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import de.danoeh.antennapod.core.export.opml.OpmlElement;
import de.danoeh.antennapod.core.export.opml.OpmlReader;
@ -144,9 +144,10 @@ public class OpmlBackupAgent extends BackupAgentHelper {
mChecksum = digester == null ? null : digester.digest();
for (OpmlElement opmlElem : opmlElements) {
Feed feed = new Feed(opmlElem.getXmlUrl(), null, opmlElem.getText());
DownloadRequest request = DownloadRequestCreator.create(feed).build();
DownloadServiceInterface.get().download(mContext, false, request);
feed.setItems(Collections.emptyList());
DBTasks.updateFeed(mContext, feed, false);
}
FeedUpdateManager.runOnce(mContext);
} catch (XmlPullParserException e) {
Log.e(TAG, "Error while parsing the OPML file", e);
} catch (IOException e) {

View File

@ -6,7 +6,7 @@ import android.content.Intent;
import android.util.Log;
import de.danoeh.antennapod.core.ClientConfigurator;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
/**
* Refreshes all feeds when it receives an intent
@ -20,7 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
ClientConfigurator.initialize(context);
AutoUpdateManager.runOnce(context);
FeedUpdateManager.runOnce(context);
}
}

View File

@ -1,47 +1,177 @@
package de.danoeh.antennapod.core.service;
import android.app.Notification;
import android.content.Context;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import de.danoeh.antennapod.core.ClientConfigurator;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
import de.danoeh.antennapod.core.service.download.DefaultDownloaderFactory;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.service.download.NewEpisodesNotification;
import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class FeedUpdateWorker extends Worker {
private static final String TAG = "FeedUpdateWorker";
public static final String PARAM_RUN_ONCE = "runOnce";
private final NewEpisodesNotification newEpisodesNotification;
public FeedUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
newEpisodesNotification = new NewEpisodesNotification();
}
@Override
@NonNull
public Result doWork() {
final boolean isRunOnce = getInputData().getBoolean(PARAM_RUN_ONCE, false);
Log.d(TAG, "doWork() : isRunOnce = " + isRunOnce);
ClientConfigurator.initialize(getApplicationContext());
newEpisodesNotification.loadCountersBeforeRefresh();
if (NetworkUtils.networkAvailable() && NetworkUtils.isFeedRefreshAllowed()) {
DBTasks.refreshAllFeeds(getApplicationContext(), false);
} else {
if (!NetworkUtils.networkAvailable() || !NetworkUtils.isFeedRefreshAllowed()) {
Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
return Result.retry();
}
if (!isRunOnce && UserPreferences.isAutoUpdateTimeOfDay()) {
// WorkManager does not allow to set specific time for repeated tasks.
// We repeatedly schedule a OneTimeWorkRequest instead.
AutoUpdateManager.restartUpdateAlarm(getApplicationContext());
List<Feed> toUpdate;
long feedId = getInputData().getLong(FeedUpdateManager.EXTRA_FEED_ID, -1);
if (feedId == -1) { // Update all
toUpdate = DBReader.getFeedList();
Iterator<Feed> itr = toUpdate.iterator();
while (itr.hasNext()) {
Feed feed = itr.next();
if (!feed.getPreferences().getKeepUpdated()) {
itr.remove();
}
}
Collections.shuffle(toUpdate); // If the worker gets cancelled early, every feed has a chance to be updated
refreshFeeds(toUpdate, false);
} else {
toUpdate = new ArrayList<>();
Feed feed = DBReader.getFeed(feedId);
if (feed == null) {
return Result.success();
}
toUpdate.add(feed);
refreshFeeds(toUpdate, true);
}
return Result.success();
}
@NonNull
private ForegroundInfo createForegroundInfo(List<Feed> toUpdate) {
Context context = getApplicationContext();
String contentText = context.getResources().getQuantityString(R.plurals.downloads_left,
toUpdate.size(), toUpdate.size());
String bigText = Stream.of(toUpdate).map(feed -> "" + feed.getTitle()).collect(Collectors.joining("\n"));
Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_DOWNLOADING)
.setContentTitle(context.getString(R.string.download_notification_title_feeds))
.setContentText(contentText)
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
.setSmallIcon(R.drawable.ic_notification_sync)
.setOngoing(true)
.addAction(R.drawable.ic_cancel, context.getString(R.string.cancel_label),
WorkManager.getInstance(context).createCancelPendingIntent(getId()))
.build();
return new ForegroundInfo(R.id.notification_updating_feeds, notification);
}
private void refreshFeeds(List<Feed> toUpdate, boolean force) {
while (!toUpdate.isEmpty()) {
if (isStopped()) {
return;
}
setForegroundAsync(createForegroundInfo(toUpdate));
Feed feed = toUpdate.get(0);
try {
if (feed.isLocalFeed()) {
LocalFeedUpdater.updateFeed(feed, getApplicationContext(), null);
} else {
refreshFeed(feed, force);
}
} catch (Exception e) {
DBWriter.setFeedLastUpdateFailed(feed.getId(), true);
DownloadStatus status = new DownloadStatus(feed, feed.getTitle(),
DownloadError.ERROR_IO_ERROR, false, e.getMessage(), true);
DBWriter.addDownloadStatus(status);
}
toUpdate.remove(0);
}
}
void refreshFeed(Feed feed, boolean force) throws Exception {
boolean nextPage = getInputData().getBoolean(FeedUpdateManager.EXTRA_NEXT_PAGE, false)
&& feed.getNextPageLink() != null;
if (nextPage) {
feed.setPageNr(feed.getPageNr() + 1);
}
DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
builder.setForce(force || feed.hasLastUpdateFailed());
if (nextPage) {
builder.setSource(feed.getNextPageLink());
}
DownloadRequest request = builder.build();
Downloader downloader = new DefaultDownloaderFactory().create(request);
if (downloader == null) {
throw new Exception("Unable to create downloader");
}
downloader.call();
if (!downloader.getResult().isSuccessful()) {
if (downloader.getResult().isCancelled()) {
return;
}
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
DBWriter.addDownloadStatus(downloader.getResult());
return;
}
FeedSyncTask feedSyncTask = new FeedSyncTask(getApplicationContext(), request);
boolean success = feedSyncTask.run();
if (!success) {
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
DBWriter.addDownloadStatus(feedSyncTask.getDownloadStatus());
return;
}
if (request.getFeedfileId() == 0) {
return; // No download logs for new subscriptions
}
// we create a 'successful' download log if the feed's last refresh failed
List<DownloadStatus> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
if (log.size() > 0 && !log.get(0).isSuccessful()) {
DBWriter.addDownloadStatus(feedSyncTask.getDownloadStatus());
}
newEpisodesNotification.showIfNeeded(getApplicationContext(), feedSyncTask.getSavedFeed());
if (downloader.permanentRedirectUrl != null) {
DBWriter.updateFeedDownloadURL(request.getSource(), downloader.permanentRedirectUrl);
} else if (feedSyncTask.getRedirectUrl() != null) {
DBWriter.updateFeedDownloadURL(request.getSource(), feedSyncTask.getRedirectUrl());
}
}
}

View File

@ -10,19 +10,29 @@ import android.content.IntentFilter;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.ServiceCompat;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
import de.danoeh.antennapod.core.service.download.handler.PostDownloaderTask;
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.EpisodeCleanupAlgorithmFactory;
import de.danoeh.antennapod.core.util.download.ConnectionStateMonitor;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.apache.commons.io.FileUtils;
import org.greenrobot.eventbus.EventBus;
@ -39,22 +49,6 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.util.download.ConnectionStateMonitor;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.handler.FailedDownloadHandler;
import de.danoeh.antennapod.core.service.download.handler.FeedSyncTask;
import de.danoeh.antennapod.core.service.download.handler.MediaDownloadedHandler;
import de.danoeh.antennapod.core.service.download.handler.PostDownloaderTask;
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.model.download.DownloadError;
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued via the startService intent.
* The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUESTS field of
@ -69,7 +63,6 @@ public class DownloadService extends Service {
public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.core.service.cancelAll";
public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
public static final String EXTRA_REQUESTS = "downloadRequests";
public static final String EXTRA_REFRESH_ALL = "refreshAll";
public static final String EXTRA_INITIATED_BY_USER = "initiatedByUser";
public static final String EXTRA_CLEANUP_MEDIA = "cleanupMedia";
@ -85,7 +78,6 @@ public class DownloadService extends Service {
private final List<DownloadStatus> reportQueue = new ArrayList<>();
private final List<DownloadRequest> failedRequestsForReport = new ArrayList<>();
private DownloadServiceNotification notificationManager;
private final NewEpisodesNotification newEpisodesNotification;
private NotificationUpdater notificationUpdater;
private ScheduledFuture<?> notificationUpdaterFuture;
private ScheduledFuture<?> downloadPostFuture;
@ -99,16 +91,12 @@ public class DownloadService extends Service {
}
public DownloadService() {
newEpisodesNotification = new NewEpisodesNotification();
downloadEnqueueExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "EnqueueThread");
t.setPriority(Thread.MIN_PRIORITY);
return t;
});
// Must be the first runnable in syncExecutor
downloadEnqueueExecutor.execute(newEpisodesNotification::loadCountersBeforeRefresh);
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
downloadHandleExecutor = Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
r -> {
@ -140,18 +128,6 @@ public class DownloadService extends Service {
connectionMonitor.enable(getApplicationContext());
}
public static boolean isDownloadingFeeds() {
if (!isRunning) {
return false;
}
for (Downloader downloader : downloads) {
if (downloader.request.getFeedfileType() == Feed.FEEDFILETYPE_FEED && !downloader.cancelled) {
return true;
}
}
return false;
}
public static boolean isDownloadingFile(String downloadUrl) {
if (!isRunning) {
return false;
@ -182,13 +158,6 @@ public class DownloadService extends Service {
NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report);
setupNotificationUpdaterIfNecessary();
downloadEnqueueExecutor.execute(() -> onDownloadQueued(intent));
} else if (intent != null && intent.getBooleanExtra(EXTRA_REFRESH_ALL, false)) {
Notification notification = notificationManager.updateNotifications(downloads);
startForeground(R.id.notification_downloading, notification);
NotificationManagerCompat.from(this).cancel(R.id.notification_download_report);
NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report);
setupNotificationUpdaterIfNecessary();
downloadEnqueueExecutor.execute(() -> enqueueAll(intent));
} else if (downloads.size() == 0) {
shutdown();
} else {
@ -251,61 +220,12 @@ public class DownloadService extends Service {
});
}
/**
* This method MUST NOT, in any case, throw an exception.
* Otherwise, it hangs up the refresh thread pool.
*/
private void performLocalFeedRefresh(Downloader downloader, DownloadRequest request) {
try {
Feed feed = DBReader.getFeed(request.getFeedfileId());
LocalFeedUpdater.updateFeed(feed, DownloadService.this, (scanned, totalFiles) -> {
request.setSize(totalFiles);
request.setSoFar(scanned);
request.setProgressPercent((int) (100.0 * scanned / totalFiles));
});
} catch (Exception e) {
e.printStackTrace();
}
downloadEnqueueExecutor.submit(() -> {
downloads.remove(downloader);
stopServiceIfEverythingDone();
});
}
private void handleSuccessfulDownload(Downloader downloader) {
DownloadRequest request = downloader.getDownloadRequest();
DownloadStatus status = downloader.getResult();
final int type = status.getFeedfileType();
if (type == Feed.FEEDFILETYPE_FEED) {
Log.d(TAG, "Handling completed Feed Download");
FeedSyncTask feedSyncTask = new FeedSyncTask(DownloadService.this, request);
boolean success = feedSyncTask.run();
if (success) {
if (request.getFeedfileId() == 0) {
return; // No download logs for new subscriptions
}
// we create a 'successful' download log if the feed's last refresh failed
List<DownloadStatus> log = DBReader.getFeedDownloadLog(request.getFeedfileId());
if (log.size() > 0 && !log.get(0).isSuccessful()) {
saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest());
}
if (!request.isInitiatedByUser()) {
// Was stored in the database before and not initiated manually
newEpisodesNotification.showIfNeeded(DownloadService.this, feedSyncTask.getSavedFeed());
}
if (downloader.permanentRedirectUrl != null) {
DBWriter.updateFeedDownloadURL(request.getSource(), downloader.permanentRedirectUrl);
} else if (feedSyncTask.getRedirectUrl() != null) {
DBWriter.updateFeedDownloadURL(request.getSource(), feedSyncTask.getRedirectUrl());
}
} else {
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest());
}
} else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
Log.d(TAG, "Handling completed FeedMedia Download");
MediaDownloadedHandler handler = new MediaDownloadedHandler(DownloadService.this, status, request);
handler.run();
@ -461,23 +381,6 @@ public class DownloadService extends Service {
}
}
private void enqueueAll(Intent intent) {
boolean initiatedByUser = intent.getBooleanExtra(EXTRA_INITIATED_BY_USER, false);
List<Feed> feeds = DBReader.getFeedList();
for (Feed feed : feeds) {
if (feed.getPreferences().getKeepUpdated()) {
DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
builder.withInitiatedByUser(initiatedByUser);
if (feed.hasLastUpdateFailed()) {
builder.setForce(true);
}
addNewRequest(builder.build());
}
}
postDownloaders();
stopServiceIfEverythingDone();
}
private void addNewRequest(@NonNull DownloadRequest request) {
if (isDownloadingFile(request.getSource())) {
Log.d(TAG, "Skipped enqueueing request. Already running.");
@ -487,17 +390,11 @@ public class DownloadService extends Service {
return;
}
Log.d(TAG, "Add new request: " + request.getSource());
if (request.getSource().startsWith(Feed.PREFIX_LOCAL_FOLDER)) {
Downloader downloader = new LocalFeedStubDownloader(request);
writeFileUrl(request);
Downloader downloader = downloaderFactory.create(request);
if (downloader != null) {
downloads.add(downloader);
downloadHandleExecutor.submit(() -> performLocalFeedRefresh(downloader, request));
} else {
writeFileUrl(request);
Downloader downloader = downloaderFactory.create(request);
if (downloader != null) {
downloads.add(downloader);
downloadHandleExecutor.submit(() -> performDownload(downloader));
}
downloadHandleExecutor.submit(() -> performDownload(downloader));
}
}

View File

@ -5,6 +5,7 @@ import android.content.Intent;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
@ -49,10 +50,7 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface {
}
public void refreshAllFeeds(Context context, boolean initiatedByUser) {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REFRESH_ALL, true);
launchIntent.putExtra(DownloadService.EXTRA_INITIATED_BY_USER, initiatedByUser);
ContextCompat.startForegroundService(context, launchIntent);
FeedUpdateManager.runOnce(context);
}
public void cancel(Context context, String url) {

View File

@ -1,23 +1,20 @@
package de.danoeh.antennapod.core.service.download.handler;
import android.content.Context;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
public class FeedSyncTask {
private final DownloadRequest request;
private final Context context;
private Feed savedFeed;
private final FeedParserTask task;
private FeedHandlerResult feedHandlerResult;
public FeedSyncTask(Context context, DownloadRequest request) {
this.request = request;
this.context = context;
this.task = new FeedParserTask(request);
}
@ -29,13 +26,6 @@ public class FeedSyncTask {
}
savedFeed = DBTasks.updateFeed(context, feedHandlerResult.feed, false);
// If loadAllPages=true, check if another page is available and queue it for download
final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequest.REQUEST_ARG_LOAD_ALL_PAGES);
final Feed feed = feedHandlerResult.feed;
if (loadAllPages && feed.getNextPageLink() != null) {
feed.setId(savedFeed.getId());
DBTasks.loadNextPageOfFeed(context, feed, true);
}
return true;
}

View File

@ -1,20 +1,28 @@
package de.danoeh.antennapod.core.storage;
import static android.content.Context.MODE_PRIVATE;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import de.danoeh.antennapod.storage.database.mapper.FeedCursorMapper;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
@ -29,31 +37,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.model.download.DownloadStatus;
import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.net.sync.model.EpisodeAction;
/**
* Provides methods for doing common tasks that use DBReader and DBWriter.
*/
public final class DBTasks {
private static final String TAG = "DBTasks";
private static final String PREF_NAME = "dbtasks";
private static final String PREF_LAST_REFRESH = "last_refresh";
/**
* Executor service used by the autodownloadUndownloadedEpisodes method.
*/
@ -104,68 +93,12 @@ public final class DBTasks {
}
}
/**
* Refreshes all feeds.
* It must not be from the main 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 initiatedByUser a boolean indicating if the refresh was triggered by user action.
*/
public static void refreshAllFeeds(final Context context, boolean initiatedByUser) {
DownloadServiceInterface.get().refreshAllFeeds(context, initiatedByUser);
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
SynchronizationQueueSink.syncNow();
// 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
}
/**
* Queues the next page of this Feed for download. The given Feed has to be a paged
* Feed (isPaged()=true) and must contain a nextPageLink.
*
* @param context Used for requesting the download.
* @param feed The feed whose next page should be loaded.
* @param loadAllPages True if any subsequent pages should also be loaded, false otherwise.
*/
public static void loadNextPageOfFeed(final Context context, Feed feed, boolean loadAllPages) {
if (feed.isPaged() && feed.getNextPageLink() != null) {
int pageNr = feed.getPageNr() + 1;
Feed nextFeed = new Feed(feed.getNextPageLink(), null, feed.getTitle() + "(" + pageNr + ")");
nextFeed.setPageNr(pageNr);
nextFeed.setPaged(true);
nextFeed.setId(feed.getId());
DownloadRequest.Builder builder = DownloadRequestCreator.create(nextFeed);
builder.loadAllPages(loadAllPages);
DownloadServiceInterface.get().download(context, false, builder.build());
} else {
Log.e(TAG, "loadNextPageOfFeed: Feed was either not paged or contained no nextPageLink");
}
}
public static void forceRefreshFeed(Context context, Feed feed, boolean initiatedByUser) {
forceRefreshFeed(context, feed, false, initiatedByUser);
}
public static void forceRefreshCompleteFeed(final Context context, final Feed feed) {
forceRefreshFeed(context, feed, true, true);
}
private static void forceRefreshFeed(Context context, Feed feed, boolean loadAllPages, boolean initiatedByUser) {
DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
builder.withInitiatedByUser(initiatedByUser);
builder.setForce(true);
builder.loadAllPages(loadAllPages);
DownloadServiceInterface.get().download(context, false, builder.build());
FeedUpdateManager.runOnce(context, feed);
}
/**

View File

@ -665,6 +665,15 @@ public class DBWriter {
adapter.close();
}
public static Future<?> resetPagedFeedPage(Feed feed) {
return dbExec.submit(() -> {
final PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.resetPagedFeedPage(feed);
adapter.close();
});
}
/*
* Sets the 'read'-attribute of all specified FeedItems
*
@ -698,7 +707,6 @@ public class DBWriter {
});
}
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
*

View File

@ -20,16 +20,15 @@ import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import de.danoeh.antennapod.core.util.download.FeedUpdateManager;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -153,9 +152,10 @@ public class SyncService extends Worker {
continue;
}
if (!UrlChecker.containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, null);
DownloadRequest.Builder builder = DownloadRequestCreator.create(feed);
DownloadServiceInterface.get().download(getApplicationContext(), false, builder.build());
Feed feed = new Feed(downloadUrl, null, "Unknown podcast");
feed.setItems(Collections.emptyList());
Feed newFeed = DBTasks.updateFeed(getApplicationContext(), feed, false);
FeedUpdateManager.runOnce(getApplicationContext(), newFeed);
}
}
@ -193,9 +193,13 @@ public class SyncService extends Worker {
private void waitForDownloadServiceCompleted() {
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_wait_for_downloads));
try {
while (DownloadService.isRunning) {
while (true) {
//noinspection BusyWait
Thread.sleep(1000);
FeedUpdateRunningEvent event = EventBus.getDefault().getStickyEvent(FeedUpdateRunningEvent.class);
if (event == null || !event.isFeedUpdateRunning) {
return;
}
}
} catch (InterruptedException e) {
e.printStackTrace();

View File

@ -1,167 +0,0 @@
package de.danoeh.antennapod.core.util.download;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.util.Arrays;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.FeedUpdateWorker;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.NetworkUtils;
public class AutoUpdateManager {
private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker";
private static final String WORK_ID_FEED_UPDATE_ONCE = WORK_ID_FEED_UPDATE + "Once";
private static final String TAG = "AutoUpdateManager";
private AutoUpdateManager() {
}
/**
* Start / restart periodic auto feed refresh
* @param context Context
*/
public static void restartUpdateAlarm(Context context) {
if (UserPreferences.isAutoUpdateDisabled()) {
disableAutoUpdate(context);
} else if (UserPreferences.isAutoUpdateTimeOfDay()) {
int[] timeOfDay = UserPreferences.getUpdateTimeOfDay();
Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1], context);
} else {
long milliseconds = UserPreferences.getUpdateInterval();
restartUpdateIntervalAlarm(milliseconds, context);
}
}
/**
* Sets the interval in which the feeds are refreshed automatically
*/
private static void restartUpdateIntervalAlarm(long intervalMillis, Context context) {
Log.d(TAG, "Restarting update alarm.");
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(FeedUpdateWorker.class,
intervalMillis, TimeUnit.MILLISECONDS)
.setConstraints(getConstraints())
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
WORK_ID_FEED_UPDATE, ExistingPeriodicWorkPolicy.REPLACE, workRequest);
}
/**
* Sets time of day the feeds are refreshed automatically
*/
private static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute, Context context) {
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);
}
long triggerAtMillis = alarm.getTimeInMillis() - now.getTimeInMillis();
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
.setConstraints(getConstraints())
.setInitialDelay(triggerAtMillis, TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE,
ExistingWorkPolicy.REPLACE, workRequest);
}
/**
* Run auto feed refresh once in background, as soon as what OS scheduling allows.
*
* Callers from UI should use {@link #runImmediate(Context)}, as it will guarantee
* the refresh be run immediately.
* @param context Context
*/
public static void runOnce(Context context) {
Log.d(TAG, "Run auto update once, as soon as OS allows.");
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
.setConstraints(getConstraints())
.setInitialDelay(0L, TimeUnit.MILLISECONDS)
.setInputData(new Data.Builder()
.putBoolean(FeedUpdateWorker.PARAM_RUN_ONCE, true)
.build()
)
.build();
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE_ONCE,
ExistingWorkPolicy.REPLACE, workRequest);
}
/**
/**
* Run auto feed refresh once in background immediately, using its own thread.
*
* Callers where the additional threads is not suitable should use {@link #runOnce(Context)}
*/
public static void runImmediate(@NonNull Context context) {
Log.d(TAG, "Run auto update immediately in background.");
if (!NetworkUtils.networkAvailable()) {
Log.d(TAG, "Ignoring: No network connection.");
} else if (NetworkUtils.isFeedRefreshAllowed()) {
startRefreshAllFeeds(context);
} else {
confirmMobileAllFeedsRefresh(context);
}
}
private static void confirmMobileAllFeedsRefresh(final Context context) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.feed_refresh_title)
.setMessage(R.string.confirm_mobile_feed_refresh_dialog_message)
.setPositiveButton(R.string.confirm_mobile_streaming_button_once,
(dialog, which) -> startRefreshAllFeeds(context))
.setNeutralButton(R.string.confirm_mobile_streaming_button_always, (dialog, which) -> {
UserPreferences.setAllowMobileFeedRefresh(true);
startRefreshAllFeeds(context);
})
.setNegativeButton(R.string.no, null);
builder.show();
}
private static void startRefreshAllFeeds(final Context context) {
new Thread(() -> DBTasks.refreshAllFeeds(
context.getApplicationContext(), true), "ManualRefreshAllFeeds").start();
}
public static void disableAutoUpdate(Context context) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_ID_FEED_UPDATE);
}
private static Constraints getConstraints() {
Constraints.Builder constraints = new Constraints.Builder();
if (UserPreferences.isAllowMobileFeedRefresh()) {
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
} else {
constraints.setRequiredNetworkType(NetworkType.UNMETERED);
}
return constraints.build();
}
}

View File

@ -0,0 +1,112 @@
package de.danoeh.antennapod.core.util.download;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.service.FeedUpdateWorker;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import java.util.concurrent.TimeUnit;
public class FeedUpdateManager {
public static final String WORK_TAG_FEED_UPDATE = "feedUpdate";
private static final String WORK_ID_FEED_UPDATE = "de.danoeh.antennapod.core.service.FeedUpdateWorker";
private static final String WORK_ID_FEED_UPDATE_MANUAL = "feedUpdateManual";
public static final String EXTRA_FEED_ID = "feed_id";
public static final String EXTRA_NEXT_PAGE = "next_page";
private static final String TAG = "AutoUpdateManager";
private FeedUpdateManager() {
}
/**
* Start / restart periodic auto feed refresh
* @param context Context
*/
public static void restartUpdateAlarm(Context context, boolean replace) {
if (UserPreferences.isAutoUpdateDisabled()) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_ID_FEED_UPDATE);
} else {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
FeedUpdateWorker.class, UserPreferences.getUpdateInterval(), TimeUnit.HOURS)
.setConstraints(getConstraints())
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_ID_FEED_UPDATE,
replace ? ExistingPeriodicWorkPolicy.REPLACE : ExistingPeriodicWorkPolicy.KEEP, workRequest);
}
}
public static void runOnce(Context context) {
runOnce(context, null, false);
}
public static void runOnce(Context context, Feed feed) {
runOnce(context, feed, false);
}
public static void runOnce(Context context, Feed feed, boolean nextPage) {
OneTimeWorkRequest.Builder workRequest = new OneTimeWorkRequest.Builder(FeedUpdateWorker.class)
.setInitialDelay(0L, TimeUnit.MILLISECONDS)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.addTag(WORK_TAG_FEED_UPDATE);
if (feed != null) {
Data.Builder builder = new Data.Builder();
builder.putLong(EXTRA_FEED_ID, feed.getId());
builder.putBoolean(EXTRA_NEXT_PAGE, nextPage);
workRequest.setInputData(builder.build());
}
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_FEED_UPDATE_MANUAL,
ExistingWorkPolicy.REPLACE, workRequest.build());
}
public static void runOnceOrAsk(@NonNull Context context) {
Log.d(TAG, "Run auto update immediately in background.");
if (!NetworkUtils.networkAvailable()) {
Log.d(TAG, "Ignoring: No network connection.");
} else if (NetworkUtils.isFeedRefreshAllowed()) {
runOnce(context);
} else {
confirmMobileAllFeedsRefresh(context);
}
}
private static void confirmMobileAllFeedsRefresh(final Context context) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.feed_refresh_title)
.setMessage(R.string.confirm_mobile_feed_refresh_dialog_message)
.setPositiveButton(R.string.confirm_mobile_streaming_button_once,
(dialog, which) -> runOnce(context))
.setNeutralButton(R.string.confirm_mobile_streaming_button_always, (dialog, which) -> {
UserPreferences.setAllowMobileFeedRefresh(true);
runOnce(context);
})
.setNegativeButton(R.string.no, null);
builder.show();
}
private static Constraints getConstraints() {
Constraints.Builder constraints = new Constraints.Builder();
if (UserPreferences.isAllowMobileFeedRefresh()) {
constraints.setRequiredNetworkType(NetworkType.CONNECTED);
} else {
constraints.setRequiredNetworkType(NetworkType.UNMETERED);
}
return constraints.build();
}
}

View File

@ -25,6 +25,28 @@
<item>heavy</item>
</string-array>
<string-array name="feed_refresh_interval_entries">
<item>@string/feed_refresh_never</item>
<item>@string/feed_every_hour</item>
<item>@string/feed_every_2_hours</item>
<item>@string/feed_every_4_hours</item>
<item>@string/feed_every_8_hours</item>
<item>@string/feed_every_12_hours</item>
<item>@string/feed_every_24_hours</item>
<item>@string/feed_every_72_hours</item>
</string-array>
<string-array name="feed_refresh_interval_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>12</item>
<item>24</item>
<item>72</item>
</string-array>
<string-array name="globalNewEpisodesActionItems">
<item>@string/feed_new_episodes_action_add_to_inbox</item>
<item>@string/feed_new_episodes_action_nothing</item>

View File

@ -19,6 +19,7 @@
<item name="notification_gpodnet_sync_error" type="id"/>
<item name="notification_gpodnet_sync_autherror" type="id"/>
<item name="notification_downloading" type="id"/>
<item name="notification_updating_feeds" type="id"/>
<item name="notification_download_report" type="id"/>
<item name="notification_auto_download_report" type="id"/>
<item name="notification_playing" type="id"/>

View File

@ -0,0 +1,9 @@
package de.danoeh.antennapod.event;
public class FeedUpdateRunningEvent {
public final boolean isFeedUpdateRunning;
public FeedUpdateRunningEvent(boolean isRunning) {
this.isFeedUpdateRunning = isRunning;
}
}

View File

@ -263,7 +263,7 @@ public class DownloadRequest implements Parcelable {
public static class Builder {
private final String destination;
private final String source;
private String source;
private final String title;
private String username;
private String password;
@ -296,6 +296,10 @@ public class DownloadRequest implements Parcelable {
return this;
}
public void setSource(String source) {
this.source = source;
}
public void setForce(boolean force) {
if (force) {
lastModified = null;

View File

@ -734,6 +734,13 @@ public class PodDBAdapter {
}
}
public void resetPagedFeedPage(Feed feed) {
final String sql = "UPDATE " + TABLE_NAME_FEEDS
+ " SET " + KEY_NEXT_PAGE_LINK + "=" + KEY_DOWNLOAD_URL
+ " WHERE " + KEY_ID + "=" + feed.getId();
db.execSQL(sql);
}
public void setFeedLastUpdateFailed(long feedId, boolean failed) {
final String sql = "UPDATE " + TABLE_NAME_FEEDS
+ " SET " + KEY_LAST_UPDATE_FAILED + "=" + (failed ? "1" : "0")

View File

@ -6,14 +6,17 @@ import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import androidx.preference.PreferenceManager;
import de.danoeh.antennapod.model.download.ProxyConfig;
import de.danoeh.antennapod.model.feed.FeedCounter;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.model.feed.SubscriptionsFilter;
import de.danoeh.antennapod.model.playback.MediaType;
import org.json.JSONArray;
import org.json.JSONException;
@ -28,13 +31,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.model.feed.FeedCounter;
import de.danoeh.antennapod.model.playback.MediaType;
import de.danoeh.antennapod.model.feed.SubscriptionsFilter;
import de.danoeh.antennapod.model.download.ProxyConfig;
import de.danoeh.antennapod.model.feed.SortOrder;
/**
* Provides access to preferences set by the user in the settings screen. A
@ -463,34 +459,12 @@ public class UserPreferences {
return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true);
}
/*
* Returns update interval in milliseconds; value 0 means that auto update is disabled
* or feeds are updated at a certain time of day
*/
public static long getUpdateInterval() {
String updateInterval = prefs.getString(PREF_UPDATE_INTERVAL, "0");
if(!updateInterval.contains(":")) {
return readUpdateInterval(updateInterval);
} else {
return 0;
}
}
public static int[] getUpdateTimeOfDay() {
String datetime = prefs.getString(PREF_UPDATE_INTERVAL, "");
if(datetime.length() >= 3 && datetime.contains(":")) {
String[] parts = datetime.split(":");
int hourOfDay = Integer.parseInt(parts[0]);
int minute = Integer.parseInt(parts[1]);
return new int[] { hourOfDay, minute };
} else {
return new int[0];
}
return Integer.parseInt(prefs.getString(PREF_UPDATE_INTERVAL, "12"));
}
public static boolean isAutoUpdateDisabled() {
return prefs.getString(PREF_UPDATE_INTERVAL, "").equals("0");
return getUpdateInterval() == 0;
}
private static boolean isAllowMobileFor(String type) {
@ -696,24 +670,6 @@ public class UserPreferences {
.apply();
}
public static void setUpdateInterval(long hours) {
prefs.edit()
.putString(PREF_UPDATE_INTERVAL, String.valueOf(hours))
.apply();
}
public static void setUpdateTimeOfDay(int hourOfDay, int minute) {
prefs.edit()
.putString(PREF_UPDATE_INTERVAL, hourOfDay + ":" + minute)
.apply();
}
public static void disableAutoUpdate() {
prefs.edit()
.putString(PREF_UPDATE_INTERVAL, "0")
.apply();
}
public static boolean gpodnetNotificationsEnabled() {
if (Build.VERSION.SDK_INT >= 26) {
return true; // System handles notification preferences
@ -754,11 +710,6 @@ public class UserPreferences {
.apply();
}
private static long readUpdateInterval(String valueFromPrefs) {
int hours = Integer.parseInt(valueFromPrefs);
return TimeUnit.HOURS.toMillis(hours);
}
private static List<Float> readPlaybackSpeedArray(String valueFromPrefs) {
if (valueFromPrefs != null) {
try {
@ -851,15 +802,6 @@ public class UserPreferences {
}
}
/**
*
* @return true if auto update is set to a specific time
* false if auto update is set to interval
*/
public static boolean isAutoUpdateTimeOfDay() {
return getUpdateTimeOfDay().length == 2;
}
public static String getDefaultPage() {
return prefs.getString(PREF_DEFAULT_PAGE, "HomeFragment");
}

View File

@ -412,15 +412,15 @@
<string name="network_pref">Network</string>
<string name="network_pref_sum">Update interval, Download controls, Mobile data</string>
<string name="feed_refresh_title">Refresh podcasts</string>
<string name="feed_refresh_sum">Specify an interval or a specific time to look for new episodes automatically</string>
<string name="feed_refresh_interval">Interval</string>
<string name="feed_refresh_time">Time</string>
<string name="feed_refresh_sum">Specify an interval at which AntennaPod looks for new episodes automatically</string>
<string name="feed_refresh_never">Never</string>
<string name="feed_refresh_interval_at">at %1$s</string>
<plurals name="feed_refresh_every_x_hours">
<item quantity="one">Every hour</item>
<item quantity="other">Every %d hours</item>
</plurals>
<string name="feed_every_hour">Every hour</string>
<string name="feed_every_2_hours">Every 2 hours</string>
<string name="feed_every_4_hours">Every 4 hours</string>
<string name="feed_every_8_hours">Every 8 hours</string>
<string name="feed_every_12_hours">Every 12 hours</string>
<string name="feed_every_24_hours">Every day</string>
<string name="feed_every_72_hours">Every 3 days</string>
<string name="pref_followQueue_title">Continuous Playback</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Headphones or Bluetooth disconnect</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Headphones Reconnect</string>
@ -499,7 +499,6 @@
<string name="open_bug_tracker">Open bug tracker</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="pref_current_value">Current value: %1$s</string>
<string name="pref_proxy_title">Proxy</string>
<string name="pref_proxy_sum">Set a network proxy</string>
<string name="pref_no_browser_found">No web browser found.</string>