Convert playback history fragment to lazy loading (#5886)

This commit is contained in:
Paul Ganssle 2022-06-09 16:24:22 -04:00 committed by GitHub
parent fd066a648b
commit df53c5bfe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 506 additions and 549 deletions

View File

@ -49,9 +49,9 @@ public class InboxFragment extends EpisodesListFragment implements Toolbar.OnMen
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View inboxContainer = View.inflate(getContext(), R.layout.inbox_fragment, null);
View inboxContainer = View.inflate(getContext(), R.layout.list_container_fragment, null);
View root = super.onCreateView(inflater, container, savedInstanceState);
((FrameLayout) inboxContainer.findViewById(R.id.inboxContent)).addView(root);
((FrameLayout) inboxContainer.findViewById(R.id.listContent)).addView(root);
emptyView.setTitle(R.string.no_inbox_head_label);
emptyView.setMessage(R.string.no_inbox_label);

View File

@ -1,57 +1,36 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
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.event.FeedItemEvent;
import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.EmptyViewHandler;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
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.List;
public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
public class PlaybackHistoryFragment extends EpisodesListFragment implements Toolbar.OnMenuItemClickListener {
public static final String TAG = "PlaybackHistoryFragment";
private static final String KEY_UP_ARROW = "up_arrow";
private List<FeedItem> playbackHistory;
private PlaybackHistoryListAdapter adapter;
private Disposable disposable;
private EpisodeItemListRecyclerView recyclerView;
private EmptyViewHandler emptyView;
private ProgressBar progressBar;
private Toolbar toolbar;
private boolean displayUpArrow;
@ -64,8 +43,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.simple_list_fragment, container, false);
toolbar = root.findViewById(R.id.toolbar);
View historyContainer = View.inflate(getContext(), R.layout.list_container_fragment, null);
View root = super.onCreateView(inflater, container, savedInstanceState);
((FrameLayout) historyContainer.findViewById(R.id.listContent)).addView(root);
toolbar = historyContainer.findViewById(R.id.toolbar);
toolbar.setTitle(R.string.playback_history_label);
toolbar.setOnMenuItemClickListener(this);
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
@ -76,34 +59,14 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
toolbar.inflateMenu(R.menu.playback_history);
refreshToolbarState();
recyclerView = root.findViewById(R.id.recyclerView);
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
adapter = new PlaybackHistoryListAdapter((MainActivity) getActivity());
recyclerView.setAdapter(adapter);
progressBar = root.findViewById(R.id.progLoading);
listAdapter = new PlaybackHistoryListAdapter((MainActivity) getActivity());
recyclerView.setAdapter(listAdapter);
emptyView = new EmptyViewHandler(getActivity());
emptyView.setIcon(R.drawable.ic_history);
emptyView.setTitle(R.string.no_history_head_label);
emptyView.setMessage(R.string.no_history_label);
emptyView.attachToRecyclerView(recyclerView);
return root;
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
loadItems();
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
if (disposable != null) {
disposable.dispose();
}
return historyContainer;
}
@Override
@ -112,55 +75,8 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
super.onSaveInstanceState(outState);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
if (playbackHistory == null) {
return;
} else if (adapter == null) {
loadItems();
return;
}
for (int i = 0, size = event.items.size(); i < size; i++) {
FeedItem item = event.items.get(i);
int pos = FeedItemUtil.indexOfItemWithId(playbackHistory, item.getId());
if (pos >= 0) {
playbackHistory.remove(pos);
playbackHistory.add(pos, item);
adapter.notifyItemChangedCompat(pos);
}
}
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(DownloadEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
DownloaderUpdate update = event.update;
if (adapter != null && update.mediaIds.length > 0) {
for (long mediaId : update.mediaIds) {
int pos = FeedItemUtil.indexOfItemWithMediaId(playbackHistory, mediaId);
if (pos >= 0) {
adapter.notifyItemChangedCompat(pos);
}
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(PlaybackPositionEvent event) {
if (adapter != null) {
for (int i = 0; i < adapter.getItemCount(); i++) {
EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i);
if (holder != null && holder.isCurrentlyPlayingItem()) {
holder.notifyPlaybackPositionUpdated(event);
break;
}
}
}
}
public void refreshToolbarState() {
boolean hasHistory = playbackHistory != null && !playbackHistory.isEmpty();
boolean hasHistory = episodes != null && !episodes.isEmpty();
toolbar.getMenu().findItem(R.id.clear_history_item).setVisible(hasHistory);
}
@ -173,33 +89,6 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
return false;
}
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
}
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onKeyUp(KeyEvent event) {
if (!isAdded() || !isVisible() || !isMenuVisible()) {
return;
}
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_T:
recyclerView.smoothScrollToPosition(0);
break;
case KeyEvent.KEYCODE_B:
recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1);
break;
default:
break;
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onHistoryUpdated(PlaybackHistoryEvent event) {
loadItems();
@ -212,59 +101,47 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
refreshToolbarState();
}
@Override
@Subscribe(threadMode = ThreadMode.MAIN)
public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
loadItems();
refreshToolbarState();
}
private void onFragmentLoaded() {
adapter.notifyDataSetChanged();
@Override
protected void onFragmentLoaded(List<FeedItem> episodes) {
super.onFragmentLoaded(episodes);
listAdapter.notifyDataSetChanged();
refreshToolbarState();
}
private void loadItems() {
if (disposable != null) {
disposable.dispose();
}
progressBar.setVisibility(View.VISIBLE);
emptyView.hide();
disposable = Observable.fromCallable(this::loadData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
progressBar.setVisibility(View.GONE);
playbackHistory = result;
adapter.updateItems(playbackHistory);
onFragmentLoaded();
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
@NonNull
private List<FeedItem> loadData() {
List<FeedItem> history = DBReader.getPlaybackHistory();
DBReader.loadAdditionalFeedItemListData(history);
return history;
}
private class PlaybackHistoryListAdapter extends EpisodeItemListAdapter {
public PlaybackHistoryListAdapter(MainActivity mainActivity) {
super(mainActivity);
}
@Override
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
// played items shouldn't be transparent for this fragment since, *all* items
// in this fragment will, by definition, be played. So it serves no purpose and can make
// it harder to read.
holder.itemView.setAlpha(1.0f);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuItemUtils.setOnClickListeners(menu, PlaybackHistoryFragment.this::onContextItemSelected);
}
}
@NonNull
@Override
protected List<FeedItem> loadData() {
return DBReader.getPlaybackHistory(0, page * EPISODES_PER_PAGE);
}
@NonNull
@Override
protected List<FeedItem> loadMoreData(int page) {
return DBReader.getPlaybackHistory((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
}
@Override
protected int loadTotalItemCount() {
return (int) DBReader.getPlaybackHistoryLength();
}
}

View File

@ -9,13 +9,13 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true"
app:title="@string/inbox_label" />
<FrameLayout
android:id="@+id/inboxContent"
android:id="@+id/listContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />

View File

@ -45,11 +45,6 @@ public final class DBReader {
private static final String TAG = "DBReader";
/**
* Maximum size of the list returned by {@link #getPlaybackHistory()}.
*/
public static final int PLAYBACK_HISTORY_SIZE = 50;
/**
* Maximum size of the list returned by {@link #getDownloadLog()}.
*/
@ -419,11 +414,12 @@ public final class DBReader {
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
* has been completed at least once.
*
* @param limit The maximum number of items to return.
*
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
*/
@NonNull
public static List<FeedItem> getPlaybackHistory() {
public static List<FeedItem> getPlaybackHistory(int offset, int limit) {
Log.d(TAG, "getPlaybackHistory() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
@ -432,7 +428,7 @@ public final class DBReader {
Cursor mediaCursor = null;
Cursor itemCursor = null;
try {
mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
mediaCursor = adapter.getCompletedMediaCursor(offset, limit);
String[] itemIds = new String[mediaCursor.getCount()];
for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
int index = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
@ -454,6 +450,17 @@ public final class DBReader {
}
}
public static long getPlaybackHistoryLength() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try {
return adapter.getCompletedMediaLength();
} finally {
adapter.close();
}
}
/**
* Loads the download log from the database.
*

View File

@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Random;
@ -16,8 +18,11 @@ import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.storage.database.PodDBAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.RobolectricTestRunner;
import static de.danoeh.antennapod.core.storage.DbTestUtils.saveFeedlist;
@ -31,410 +36,473 @@ import static org.junit.Assert.assertTrue;
* Test class for DBReader.
*/
@SuppressWarnings("ConstantConditions")
@RunWith(RobolectricTestRunner.class)
@RunWith(Enclosed.class)
public class DbReaderTest {
@Ignore("Not a test")
public static class TestBase {
@Before
public void setUp() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
UserPreferences.init(context);
@Before
public void setUp() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
UserPreferences.init(context);
PodDBAdapter.init(context);
PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.close();
}
PodDBAdapter.init(context);
PodDBAdapter.deleteDatabase();
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.close();
}
@After
public void tearDown() {
PodDBAdapter.tearDownTests();
DBWriter.tearDownTests();
}
@Test
public void testGetFeedList() {
List<Feed> feeds = saveFeedlist(10, 0, false);
List<Feed> savedFeeds = DBReader.getFeedList();
assertNotNull(savedFeeds);
assertEquals(feeds.size(), savedFeeds.size());
for (int i = 0; i < feeds.size(); i++) {
assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
@After
public void tearDown() {
PodDBAdapter.tearDownTests();
DBWriter.tearDownTests();
}
}
@Test
public void testGetFeedListSortOrder() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", true);
Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", true);
Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", true);
Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", true);
adapter.setCompleteFeed(feed1);
adapter.setCompleteFeed(feed2);
adapter.setCompleteFeed(feed3);
adapter.setCompleteFeed(feed4);
assertTrue(feed1.getId() != 0);
assertTrue(feed2.getId() != 0);
assertTrue(feed3.getId() != 0);
assertTrue(feed4.getId() != 0);
adapter.close();
List<Feed> saved = DBReader.getFeedList();
assertNotNull(saved);
assertEquals("Wrong size: ", 4, saved.size());
assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
}
@Test
public void testFeedListDownloadUrls() {
List<Feed> feeds = saveFeedlist(10, 0, false);
List<String> urls = DBReader.getFeedListDownloadUrls();
assertNotNull(urls);
assertEquals(feeds.size(), urls.size());
for (int i = 0; i < urls.size(); i++) {
assertEquals(urls.get(i), feeds.get(i).getDownload_url());
}
}
@Test
public void testLoadFeedDataOfFeedItemlist() {
final int numFeeds = 10;
final int numItems = 1;
List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
for (FeedItem item : f.getItems()) {
item.setFeed(null);
item.setFeedId(f.getId());
items.add(item);
@RunWith(RobolectricTestRunner.class)
public static class SingleTests extends TestBase {
@Test
public void testGetFeedList() {
List<Feed> feeds = saveFeedlist(10, 0, false);
List<Feed> savedFeeds = DBReader.getFeedList();
assertNotNull(savedFeeds);
assertEquals(feeds.size(), savedFeeds.size());
for (int i = 0; i < feeds.size(); i++) {
assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
}
}
DBReader.loadAdditionalFeedItemListData(items);
for (int i = 0; i < numFeeds; i++) {
for (int j = 0; j < numItems; j++) {
FeedItem item = feeds.get(i).getItems().get(j);
assertNotNull(item.getFeed());
assertEquals(feeds.get(i).getId(), item.getFeed().getId());
assertEquals(item.getFeed().getId(), item.getFeedId());
@Test
public void testGetFeedListSortOrder() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", true);
Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", true);
Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", true);
Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", true);
adapter.setCompleteFeed(feed1);
adapter.setCompleteFeed(feed2);
adapter.setCompleteFeed(feed3);
adapter.setCompleteFeed(feed4);
assertTrue(feed1.getId() != 0);
assertTrue(feed2.getId() != 0);
assertTrue(feed3.getId() != 0);
assertTrue(feed4.getId() != 0);
adapter.close();
List<Feed> saved = DBReader.getFeedList();
assertNotNull(saved);
assertEquals("Wrong size: ", 4, saved.size());
assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
}
@Test
public void testFeedListDownloadUrls() {
List<Feed> feeds = saveFeedlist(10, 0, false);
List<String> urls = DBReader.getFeedListDownloadUrls();
assertNotNull(urls);
assertEquals(feeds.size(), urls.size());
for (int i = 0; i < urls.size(); i++) {
assertEquals(urls.get(i), feeds.get(i).getDownload_url());
}
}
}
@Test
public void testGetFeedItemList() {
final int numFeeds = 1;
final int numItems = 10;
Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
List<FeedItem> items = feed.getItems();
feed.setItems(null);
List<FeedItem> savedItems = DBReader.getFeedItemList(feed);
assertNotNull(savedItems);
assertEquals(items.size(), savedItems.size());
for (int i = 0; i < savedItems.size(); i++) {
assertEquals(savedItems.get(i).getId(), items.get(i).getId());
}
}
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveQueue(int numItems) {
if (numItems <= 0) {
throw new IllegalArgumentException("numItems<=0");
}
List<Feed> feeds = saveFeedlist(numItems, numItems, false);
List<FeedItem> allItems = new ArrayList<>();
for (Feed f : feeds) {
allItems.addAll(f.getItems());
}
// take random items from every feed
Random random = new Random();
List<FeedItem> queue = new ArrayList<>();
while (queue.size() < numItems) {
int index = random.nextInt(numItems);
if (!queue.contains(allItems.get(index))) {
queue.add(allItems.get(index));
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setQueue(queue);
adapter.close();
return queue;
}
@Test
public void testGetQueueIdList() {
final int numItems = 10;
List<FeedItem> queue = saveQueue(numItems);
LongList ids = DBReader.getQueueIDList();
assertNotNull(ids);
assertEquals(ids.size(), queue.size());
for (int i = 0; i < queue.size(); i++) {
assertTrue(ids.get(i) != 0);
assertEquals(ids.get(i), queue.get(i).getId());
}
}
@Test
public void testGetQueue() {
final int numItems = 10;
List<FeedItem> queue = saveQueue(numItems);
List<FeedItem> savedQueue = DBReader.getQueue();
assertNotNull(savedQueue);
assertEquals(savedQueue.size(), queue.size());
for (int i = 0; i < queue.size(); i++) {
assertTrue(savedQueue.get(i).getId() != 0);
assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
}
}
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveDownloadedItems(int numItems) {
if (numItems <= 0) {
throw new IllegalArgumentException("numItems<=0");
}
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
items.addAll(f.getItems());
}
List<FeedItem> downloaded = new ArrayList<>();
Random random = new Random();
while (downloaded.size() < numItems) {
int i = random.nextInt(numItems);
if (!downloaded.contains(items.get(i))) {
FeedItem item = items.get(i);
item.getMedia().setDownloaded(true);
item.getMedia().setFile_url("file" + i);
downloaded.add(item);
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.storeFeedItemlist(downloaded);
adapter.close();
return downloaded;
}
@Test
public void testGetDownloadedItems() {
final int numItems = 10;
List<FeedItem> downloaded = saveDownloadedItems(numItems);
List<FeedItem> downloadedSaved = DBReader.getDownloadedItems();
assertNotNull(downloadedSaved);
assertEquals(downloaded.size(), downloadedSaved.size());
for (FeedItem item : downloadedSaved) {
assertNotNull(item.getMedia());
assertTrue(item.getMedia().isDownloaded());
assertNotNull(item.getMedia().getDownload_url());
}
}
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveNewItems(int numItems) {
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
items.addAll(f.getItems());
}
List<FeedItem> newItems = new ArrayList<>();
Random random = new Random();
while (newItems.size() < numItems) {
int i = random.nextInt(numItems);
if (!newItems.contains(items.get(i))) {
FeedItem item = items.get(i);
item.setNew();
newItems.add(item);
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.storeFeedItemlist(newItems);
adapter.close();
return newItems;
}
@Test
public void testGetNewItemIds() {
final int numItems = 10;
List<FeedItem> newItems = saveNewItems(numItems);
long[] unreadIds = new long[newItems.size()];
for (int i = 0; i < newItems.size(); i++) {
unreadIds[i] = newItems.get(i).getId();
}
List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
assertNotNull(newItemsSaved);
assertEquals(newItemsSaved.size(), newItems.size());
for (FeedItem feedItem : newItemsSaved) {
long savedId = feedItem.getId();
boolean found = false;
for (long id : unreadIds) {
if (id == savedId) {
found = true;
break;
@Test
public void testLoadFeedDataOfFeedItemlist() {
final int numFeeds = 10;
final int numItems = 1;
List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
for (FeedItem item : f.getItems()) {
item.setFeed(null);
item.setFeedId(f.getId());
items.add(item);
}
}
assertTrue(found);
}
}
@Test
public void testGetPlaybackHistory() {
final int numItems = (DBReader.PLAYBACK_HISTORY_SIZE + 1) * 2;
final int playedItems = DBReader.PLAYBACK_HISTORY_SIZE + 1;
final int numReturnedItems = Math.min(playedItems, DBReader.PLAYBACK_HISTORY_SIZE);
final int numFeeds = 1;
Feed feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
long[] ids = new long[playedItems];
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int i = 0; i < playedItems; i++) {
FeedMedia m = feed.getItems().get(i).getMedia();
m.setPlaybackCompletionDate(new Date(i + 1));
adapter.setFeedMediaPlaybackCompletionDate(m);
ids[ids.length - 1 - i] = m.getItem().getId();
}
adapter.close();
List<FeedItem> saved = DBReader.getPlaybackHistory();
assertNotNull(saved);
assertEquals("Wrong size: ", numReturnedItems, saved.size());
for (int i = 0; i < numReturnedItems; i++) {
FeedItem item = saved.get(i);
assertNotNull(item.getMedia().getPlaybackCompletionDate());
assertEquals("Wrong sort order: ", item.getId(), ids[i]);
}
}
@Test
public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
final int numFeeds = 10;
final int numItems = 10;
DbTestUtils.saveFeedlist(numFeeds, numItems, true);
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
assertEquals(numFeeds, navDrawerData.items.size());
assertEquals(0, navDrawerData.numNewItems);
assertEquals(0, navDrawerData.queueSize);
}
@Test
public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
final int numFeeds = 10;
final int numItems = 10;
final int numQueue = 1;
final int numNew = 2;
List<Feed> feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true);
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int i = 0; i < numNew; i++) {
FeedItem item = feeds.get(0).getItems().get(i);
item.setNew();
adapter.setSingleFeedItem(item);
}
List<FeedItem> queue = new ArrayList<>();
for (int i = 0; i < numQueue; i++) {
FeedItem item = feeds.get(1).getItems().get(i);
queue.add(item);
}
adapter.setQueue(queue);
adapter.close();
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
assertEquals(numFeeds, navDrawerData.items.size());
assertEquals(numNew, navDrawerData.numNewItems);
assertEquals(numQueue, navDrawerData.queueSize);
}
@Test
public void testGetFeedItemlistCheckChaptersFalse() {
List<Feed> feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertFalse(item.hasChapters());
DBReader.loadAdditionalFeedItemListData(items);
for (int i = 0; i < numFeeds; i++) {
for (int j = 0; j < numItems; j++) {
FeedItem item = feeds.get(i).getItems().get(j);
assertNotNull(item.getFeed());
assertEquals(feeds.get(i).getId(), item.getFeed().getId());
assertEquals(item.getFeed().getId(), item.getFeedId());
}
}
}
}
@Test
public void testGetFeedItemlistCheckChaptersTrue() {
List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertTrue(item.hasChapters());
@Test
public void testGetFeedItemList() {
final int numFeeds = 1;
final int numItems = 10;
Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
List<FeedItem> items = feed.getItems();
feed.setItems(null);
List<FeedItem> savedItems = DBReader.getFeedItemList(feed);
assertNotNull(savedItems);
assertEquals(items.size(), savedItems.size());
for (int i = 0; i < savedItems.size(); i++) {
assertEquals(savedItems.get(i).getId(), items.get(i).getId());
}
}
}
@Test
public void testLoadChaptersOfFeedItemNoChapters() {
List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
saveFeedlist(1, 3, false, true, 3);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertFalse(item.hasChapters());
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
assertFalse(item.hasChapters());
assertNull(item.getChapters());
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveQueue(int numItems) {
if (numItems <= 0) {
throw new IllegalArgumentException("numItems<=0");
}
List<Feed> feeds = saveFeedlist(numItems, numItems, false);
List<FeedItem> allItems = new ArrayList<>();
for (Feed f : feeds) {
allItems.addAll(f.getItems());
}
// take random items from every feed
Random random = new Random();
List<FeedItem> queue = new ArrayList<>();
while (queue.size() < numItems) {
int index = random.nextInt(numItems);
if (!queue.contains(allItems.get(index))) {
queue.add(allItems.get(index));
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setQueue(queue);
adapter.close();
return queue;
}
@Test
public void testGetQueueIdList() {
final int numItems = 10;
List<FeedItem> queue = saveQueue(numItems);
LongList ids = DBReader.getQueueIDList();
assertNotNull(ids);
assertEquals(ids.size(), queue.size());
for (int i = 0; i < queue.size(); i++) {
assertTrue(ids.get(i) != 0);
assertEquals(ids.get(i), queue.get(i).getId());
}
}
}
@Test
public void testLoadChaptersOfFeedItemWithChapters() {
final int numChapters = 3;
DbTestUtils.saveFeedlist(1, 3, false, false, 0);
List<Feed> feeds = saveFeedlist(1, 3, false, true, numChapters);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertTrue(item.hasChapters());
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
assertTrue(item.hasChapters());
assertNotNull(item.getChapters());
assertEquals(numChapters, item.getChapters().size());
@Test
public void testGetQueue() {
final int numItems = 10;
List<FeedItem> queue = saveQueue(numItems);
List<FeedItem> savedQueue = DBReader.getQueue();
assertNotNull(savedQueue);
assertEquals(savedQueue.size(), queue.size());
for (int i = 0; i < queue.size(); i++) {
assertTrue(savedQueue.get(i).getId() != 0);
assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
}
}
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveDownloadedItems(int numItems) {
if (numItems <= 0) {
throw new IllegalArgumentException("numItems<=0");
}
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
items.addAll(f.getItems());
}
List<FeedItem> downloaded = new ArrayList<>();
Random random = new Random();
while (downloaded.size() < numItems) {
int i = random.nextInt(numItems);
if (!downloaded.contains(items.get(i))) {
FeedItem item = items.get(i);
item.getMedia().setDownloaded(true);
item.getMedia().setFile_url("file" + i);
downloaded.add(item);
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.storeFeedItemlist(downloaded);
adapter.close();
return downloaded;
}
@Test
public void testGetDownloadedItems() {
final int numItems = 10;
List<FeedItem> downloaded = saveDownloadedItems(numItems);
List<FeedItem> downloadedSaved = DBReader.getDownloadedItems();
assertNotNull(downloadedSaved);
assertEquals(downloaded.size(), downloadedSaved.size());
for (FeedItem item : downloadedSaved) {
assertNotNull(item.getMedia());
assertTrue(item.getMedia().isDownloaded());
assertNotNull(item.getMedia().getDownload_url());
}
}
@SuppressWarnings("SameParameterValue")
private List<FeedItem> saveNewItems(int numItems) {
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
List<FeedItem> items = new ArrayList<>();
for (Feed f : feeds) {
items.addAll(f.getItems());
}
List<FeedItem> newItems = new ArrayList<>();
Random random = new Random();
while (newItems.size() < numItems) {
int i = random.nextInt(numItems);
if (!newItems.contains(items.get(i))) {
FeedItem item = items.get(i);
item.setNew();
newItems.add(item);
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.storeFeedItemlist(newItems);
adapter.close();
return newItems;
}
@Test
public void testGetNewItemIds() {
final int numItems = 10;
List<FeedItem> newItems = saveNewItems(numItems);
long[] unreadIds = new long[newItems.size()];
for (int i = 0; i < newItems.size(); i++) {
unreadIds[i] = newItems.get(i).getId();
}
List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
assertNotNull(newItemsSaved);
assertEquals(newItemsSaved.size(), newItems.size());
for (FeedItem feedItem : newItemsSaved) {
long savedId = feedItem.getId();
boolean found = false;
for (long id : unreadIds) {
if (id == savedId) {
found = true;
break;
}
}
assertTrue(found);
}
}
@Test
public void testGetPlaybackHistoryLength() {
final int totalItems = 100;
Feed feed = DbTestUtils.saveFeedlist(1, totalItems, true).get(0);
PodDBAdapter adapter = PodDBAdapter.getInstance();
for (int playedItems : Arrays.asList(0, 1, 20, 100)) {
adapter.open();
for (int i = 0; i < playedItems; ++i) {
FeedMedia m = feed.getItems().get(i).getMedia();
m.setPlaybackCompletionDate(new Date(i + 1));
adapter.setFeedMediaPlaybackCompletionDate(m);
}
adapter.close();
long len = DBReader.getPlaybackHistoryLength();
assertEquals("Wrong size: ", (int) len, playedItems);
}
}
@Test
public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
final int numFeeds = 10;
final int numItems = 10;
DbTestUtils.saveFeedlist(numFeeds, numItems, true);
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
assertEquals(numFeeds, navDrawerData.items.size());
assertEquals(0, navDrawerData.numNewItems);
assertEquals(0, navDrawerData.queueSize);
}
@Test
public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
final int numFeeds = 10;
final int numItems = 10;
final int numQueue = 1;
final int numNew = 2;
List<Feed> feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true);
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int i = 0; i < numNew; i++) {
FeedItem item = feeds.get(0).getItems().get(i);
item.setNew();
adapter.setSingleFeedItem(item);
}
List<FeedItem> queue = new ArrayList<>();
for (int i = 0; i < numQueue; i++) {
FeedItem item = feeds.get(1).getItems().get(i);
queue.add(item);
}
adapter.setQueue(queue);
adapter.close();
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
assertEquals(numFeeds, navDrawerData.items.size());
assertEquals(numNew, navDrawerData.numNewItems);
assertEquals(numQueue, navDrawerData.queueSize);
}
@Test
public void testGetFeedItemlistCheckChaptersFalse() {
List<Feed> feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertFalse(item.hasChapters());
}
}
}
@Test
public void testGetFeedItemlistCheckChaptersTrue() {
List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertTrue(item.hasChapters());
}
}
}
@Test
public void testLoadChaptersOfFeedItemNoChapters() {
List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
saveFeedlist(1, 3, false, true, 3);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertFalse(item.hasChapters());
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
assertFalse(item.hasChapters());
assertNull(item.getChapters());
}
}
}
@Test
public void testLoadChaptersOfFeedItemWithChapters() {
final int numChapters = 3;
DbTestUtils.saveFeedlist(1, 3, false, false, 0);
List<Feed> feeds = saveFeedlist(1, 3, false, true, numChapters);
for (Feed feed : feeds) {
for (FeedItem item : feed.getItems()) {
assertTrue(item.hasChapters());
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
assertTrue(item.hasChapters());
assertNotNull(item.getChapters());
assertEquals(numChapters, item.getChapters().size());
}
}
}
@Test
public void testGetItemWithChapters() {
final int numChapters = 3;
List<Feed> feeds = saveFeedlist(1, 1, false, true, numChapters);
FeedItem item1 = feeds.get(0).getItems().get(0);
FeedItem item2 = DBReader.getFeedItem(item1.getId());
item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
assertTrue(item2.hasChapters());
assertEquals(item1.getChapters(), item2.getChapters());
}
@Test
public void testGetItemByEpisodeUrl() {
List<Feed> feeds = saveFeedlist(1, 1, true);
FeedItem item1 = feeds.get(0).getItems().get(0);
FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
item1.getMedia().getDownload_url());
assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
}
@Test
public void testGetItemByGuid() {
List<Feed> feeds = saveFeedlist(1, 1, true);
FeedItem item1 = feeds.get(0).getItems().get(0);
FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
item1.getMedia().getDownload_url());
assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
}
}
@Test
public void testGetItemWithChapters() {
final int numChapters = 3;
List<Feed> feeds = saveFeedlist(1, 1, false, true, numChapters);
FeedItem item1 = feeds.get(0).getItems().get(0);
FeedItem item2 = DBReader.getFeedItem(item1.getId());
item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
assertTrue(item2.hasChapters());
assertEquals(item1.getChapters(), item2.getChapters());
}
@RunWith(ParameterizedRobolectricTestRunner.class)
public static class PlaybackHistoryTest extends TestBase {
@Test
public void testGetItemByEpisodeUrl() {
List<Feed> feeds = saveFeedlist(1, 1, true);
FeedItem item1 = feeds.get(0).getItems().get(0);
FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
item1.getMedia().getDownload_url());
assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
}
private int paramOffset;
private int paramLimit;
@Test
public void testGetItemByGuid() {
List<Feed> feeds = saveFeedlist(1, 1, true);
FeedItem item1 = feeds.get(0).getItems().get(0);
@ParameterizedRobolectricTestRunner.Parameters
public static Collection<Object[]> data() {
List<Integer> limits = Arrays.asList(1, 20, 100);
List<Integer> offsets = Arrays.asList(0, 10, 20);
Object[][] rv = new Object[limits.size() * offsets.size()][2];
int i = 0;
for (int offset : offsets) {
for (int limit : limits) {
rv[i][0] = offset;
rv[i][1] = limit;
i++;
}
}
FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
item1.getMedia().getDownload_url());
assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
return Arrays.asList(rv);
}
public PlaybackHistoryTest(int offset, int limit) {
this.paramOffset = offset;
this.paramLimit = limit;
}
@Test
public void testGetPlaybackHistory() {
final int numItems = (paramLimit + 1) * 2;
final int playedItems = paramLimit + 1;
final int numReturnedItems = Math.min(Math.max(playedItems - paramOffset, 0), paramLimit);
final int numFeeds = 1;
Feed feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
long[] ids = new long[playedItems];
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
for (int i = 0; i < playedItems; i++) {
FeedMedia m = feed.getItems().get(i).getMedia();
m.setPlaybackCompletionDate(new Date(i + 1));
adapter.setFeedMediaPlaybackCompletionDate(m);
ids[ids.length - 1 - i] = m.getItem().getId();
}
adapter.close();
List<FeedItem> saved = DBReader.getPlaybackHistory(paramOffset, paramLimit);
assertNotNull(saved);
assertEquals(String.format("Wrong size with offset %d and limit %d: ",
paramOffset, paramLimit),
numReturnedItems, saved.size());
for (int i = 0; i < numReturnedItems; i++) {
FeedItem item = saved.get(i);
assertNotNull(item.getMedia().getPlaybackCompletionDate());
assertEquals(String.format("Wrong sort order with offset %d and limit %d: ",
paramOffset, paramLimit),
item.getId(), ids[paramOffset + i]);
}
}
}
}

View File

@ -1076,18 +1076,23 @@ public class PodDBAdapter {
* Returns a cursor which contains feed media objects with a playback
* completion date in ascending order.
*
* @param offset The row to start at.
* @param limit The maximum row count of the returned cursor. Must be an
* integer >= 0.
* @throws IllegalArgumentException if limit < 0
*/
public final Cursor getCompletedMediaCursor(int limit) {
public final Cursor getCompletedMediaCursor(int offset, int limit) {
if (limit < 0) {
throw new IllegalArgumentException("Limit must be >= 0");
}
return db.query(TABLE_NAME_FEED_MEDIA, null,
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
null, String.format(Locale.US, "%s DESC LIMIT %d", KEY_PLAYBACK_COMPLETION_DATE, limit));
null, String.format(Locale.US, "%s DESC LIMIT %d, %d", KEY_PLAYBACK_COMPLETION_DATE, offset, limit));
}
public final long getCompletedMediaLength() {
return DatabaseUtils.queryNumEntries(db, TABLE_NAME_FEED_MEDIA, KEY_PLAYBACK_COMPLETION_DATE + "> 0");
}
public final Cursor getSingleFeedMediaCursor(long id) {