diff options
40 files changed, 525 insertions, 964 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/FeedSettingsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/FeedSettingsTest.java index f3cd99b2c..861c62f1b 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/FeedSettingsTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/FeedSettingsTest.java @@ -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()); - } - } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index b790bc005..1b8100392 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -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; @@ -227,30 +223,6 @@ public class PreferencesTest { } @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); clickPreference(R.string.pref_parallel_downloads_title); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 9c771f161..7851e5b9a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -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)); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 50aabbd01..f5f3d28f6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -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(); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index 64a6b6632..10a41057c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -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()) diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedRefreshIntervalDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedRefreshIntervalDialog.java deleted file mode 100644 index 3d92fd979..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FeedRefreshIntervalDialog.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index dbc453301..737975389 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -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); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 5ba323372..18bec12ca 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -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); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index 0b264b5a3..ecc60c411 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -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)); }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index b4195ba01..ae9e003d5 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -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(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index bb22c5652..6681df4c1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -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)); }); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index cc13f4ce3..a0698229a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -13,51 +13,47 @@ 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.ui.statistics.StatisticsFragment; -import de.danoeh.antennapod.view.LiftOnScrollListener; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -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.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.menuhandler.FeedMenuHandler; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; /** * 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()); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java index 50de1e3c4..94c85abfe 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java @@ -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); } } } - - diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index 7aee499da..e9b0c0b19 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -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) { diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 875ed347e..80e7fdb1e 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -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(); + } } } } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java index c566a1fd2..788359a4e 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java @@ -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); } } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java index da3fd7b05..778b57c8c 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/home/HomeFragment.java @@ -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()); diff --git a/app/src/main/java/de/danoeh/antennapod/view/TimePicker.java b/app/src/main/java/de/danoeh/antennapod/view/TimePicker.java deleted file mode 100644 index 191f72d2e..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/TimePicker.java +++ /dev/null @@ -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(); - } - } -} diff --git a/app/src/main/res/layout/feed_refresh_dialog.xml b/app/src/main/res/layout/feed_refresh_dialog.xml deleted file mode 100644 index 5a6770a80..000000000 --- a/app/src/main/res/layout/feed_refresh_dialog.xml +++ /dev/null @@ -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> diff --git a/app/src/main/res/layout/scrollable_dialog.xml b/app/src/main/res/layout/scrollable_dialog.xml deleted file mode 100644 index 29b84ee4b..000000000 --- a/app/src/main/res/layout/scrollable_dialog.xml +++ /dev/null @@ -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> diff --git a/app/src/main/res/xml/preferences_network.xml b/app/src/main/res/xml/preferences_network.xml index 83929bb70..f9cd15e68 100644 --- a/app/src/main/res/xml/preferences_network.xml +++ b/app/src/main/res/xml/preferences_network.xml @@ -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" diff --git a/core/build.gradle b/core/build.gradle index a70a1e1c0..02c273db1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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" diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java index 9046b7165..79c6dd6bc 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java @@ -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) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java index 9ce89ebe2..e30b49280 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java @@ -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); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java index 49c5211b0..bbf0dc357 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedUpdateWorker.java @@ -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; -public class FeedUpdateWorker extends Worker { +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()); + } + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index 0e55e9a36..9c238137e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -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)); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java index 7b7e52e0e..976d8255f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java @@ -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) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java index e3010fe24..9cb1166b4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedSyncTask.java @@ -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; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index 18f157908..8b79d594c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -1,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); } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index 9b4146f15..dcee8a45a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -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. * diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java index 73f467154..2fd492cbd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java @@ -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(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java deleted file mode 100644 index 0602fc4fe..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/download/AutoUpdateManager.java +++ /dev/null @@ -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(); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java new file mode 100644 index 000000000..d1a273d4e --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/download/FeedUpdateManager.java @@ -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(); + } + +} diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 39f62a5d7..f3c0a0a3c 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -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> diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml index 87046cc0f..90d143d38 100644 --- a/core/src/main/res/values/ids.xml +++ b/core/src/main/res/values/ids.xml @@ -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"/> diff --git a/event/src/main/java/de/danoeh/antennapod/event/FeedUpdateRunningEvent.java b/event/src/main/java/de/danoeh/antennapod/event/FeedUpdateRunningEvent.java new file mode 100644 index 000000000..4c14c1647 --- /dev/null +++ b/event/src/main/java/de/danoeh/antennapod/event/FeedUpdateRunningEvent.java @@ -0,0 +1,9 @@ +package de.danoeh.antennapod.event; + +public class FeedUpdateRunningEvent { + public final boolean isFeedUpdateRunning; + + public FeedUpdateRunningEvent(boolean isRunning) { + this.isFeedUpdateRunning = isRunning; + } +} diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java index e5c6662eb..9f9737edc 100644 --- a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequest.java @@ -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; diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java index 6802dfcc3..1de47f769 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java @@ -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") diff --git a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java index 92acb6319..8a4d248ee 100644 --- a/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java +++ b/storage/preferences/src/main/java/de/danoeh/antennapod/storage/preferences/UserPreferences.java @@ -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"); } diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index f9b61de08..32db0dcac 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -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> |