@@ -162,14 +162,6 @@ public class QueueFragment extends Fragment {
                 recyclerAdapter.notifyDataSetChanged();
                 break;
             case MOVED:
-                int from = FeedItemUtil.indexOfItemWithId(queue, event.item.getId());
-                int to = event.position;
-                if(from != to) {
-                    queue.add(to, queue.remove(from));
-                    recyclerAdapter.notifyItemMoved(from, to);
-                } else {
-                    // QueueFragment itself sent the event and already moved the item
-                }
                 break;
         }
         onFragmentLoaded();
package de.danoeh.antennapod.fragment;

import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;

import java.util.List;

import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.Downloader;
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.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.greenrobot.event.EventBus;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * Shows all items in the queue
 */
public class QueueFragment extends Fragment {

    public static final String TAG = "QueueFragment";

    private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
            EventDistributor.DOWNLOAD_QUEUED |
            EventDistributor.PLAYER_STATUS_UPDATE;

    private TextView infoBar;
    private RecyclerView recyclerView;
    private QueueRecyclerAdapter recyclerAdapter;
    private TextView txtvEmpty;
    private ProgressBar progLoading;

    private List<FeedItem> queue;
    private List<Downloader> downloaderList;

    private boolean isUpdatingFeeds = false;

    private static final String PREFS = "QueueFragment";
    private static final String PREF_SCROLL_POSITION = "scroll_position";
    private static final String PREF_SCROLL_OFFSET = "scroll_offset";

    private DownloadObserver downloadObserver = null;

    /**
     * Download observer updates won't result in an upate of the list adapter if this is true.
     */
    private boolean blockDownloadObserverUpdate = false;

    private Subscription subscription;
    private LinearLayoutManager layoutManager;
    private ItemTouchHelper itemTouchHelper;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        setHasOptionsMenu(true);
    }

    @Override
    public void onStart() {
        super.onStart();
        if (downloadObserver != null) {
            downloadObserver.setActivity(getActivity());
            downloadObserver.onResume();
        }
        if (queue != null) {
            onFragmentLoaded();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        recyclerView.setAdapter(recyclerAdapter);
        loadItems();
        EventDistributor.getInstance().register(contentUpdate);
        EventBus.getDefault().register(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        saveScrollPosition();
        EventDistributor.getInstance().unregister(contentUpdate);
        EventBus.getDefault().unregister(this);
        if(subscription != null) {
            subscription.unsubscribe();
        }
    }

    public void onEventMainThread(QueueEvent event) {
        Log.d(TAG, "onEvent(" + event + ")");
        switch(event.action) {
            case ADDED:
                queue.add(event.position, event.item);
                recyclerAdapter.notifyItemInserted(event.position);
                break;
            case SET_QUEUE:
                queue = event.items;
                recyclerAdapter.notifyDataSetChanged();
                break;
            case REMOVED:
            case IRREVERSIBLE_REMOVED:
                int position = FeedItemUtil.indexOfItemWithId(queue, event.item.getId());
                queue.remove(position);
                recyclerAdapter.notifyItemRemoved(position);
                break;
            case CLEARED:
                queue.clear();
                recyclerAdapter.notifyDataSetChanged();
                break;
            case SORTED:
                queue = event.items;
                recyclerAdapter.notifyDataSetChanged();
                break;
            case MOVED:
                int from = FeedItemUtil.indexOfItemWithId(queue, event.item.getId());
                int to = event.position;
                if(from != to) {
                    queue.add(to, queue.remove(from));
                    recyclerAdapter.notifyItemMoved(from, to);
                } else {
                    // QueueFragment itself sent the event and already moved the item
                }
                break;
        }
        onFragmentLoaded();
    }

    public void onEventMainThread(FeedItemEvent event) {
        Log.d(TAG, "onEvent(" + event + ")");
        for(int i=0, size = event.items.size(); i < size; i++) {
            FeedItem item = event.items.get(i);
            int pos = FeedItemUtil.indexOfItemWithId(queue, item.getId());
            if(pos >= 0) {
                queue.remove(pos);
                queue.add(pos, item);
                recyclerAdapter.notifyItemChanged(pos);
            }
        }
    }

    private void saveScrollPosition() {
        int firstItem = layoutManager.findFirstVisibleItemPosition();
        View firstItemView = layoutManager.findViewByPosition(firstItem);
        float topOffset;
        if(firstItemView == null) {
            topOffset = 0;
        } else {
            topOffset = firstItemView.getTop();
        }

        SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt(PREF_SCROLL_POSITION, firstItem);
        editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
        editor.commit();
    }

    private void restoreScrollPosition() {
        SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
        int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
        float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
        if (position > 0 || offset > 0) {
            layoutManager.scrollToPositionWithOffset(position, (int) offset);
            // restore once, then forget
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt(PREF_SCROLL_POSITION, 0);
            editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
            editor.commit();
        }
    }

    private void resetViewState() {
        recyclerAdapter = null;
        blockDownloadObserverUpdate = false;
        if (downloadObserver != null) {
            downloadObserver.onPause();
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        resetViewState();
    }

    private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = () -> {
        return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
    };

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        if (queue != null) {
            inflater.inflate(R.menu.queue, menu);

            MenuItem searchItem = menu.findItem(R.id.action_search);
            final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
            MenuItemUtils.adjustTextColor(getActivity(), sv);
            sv.setQueryHint(getString(R.string.search_hint));
            sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String s) {
                    sv.clearFocus();
                    ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s));
                    return true;
                }

                @Override
                public boolean onQueryTextChange(String s) {
                    return false;
                }
            });

            MenuItemUtils.refreshLockItem(getActivity(), menu);

            isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (!super.onOptionsItemSelected(item)) {
            switch (item.getItemId()) {
                case R.id.queue_lock:
                    boolean locked = !UserPreferences.isQueueLocked();
                    UserPreferences.setQueueLocked(locked);
                    getActivity().supportInvalidateOptionsMenu();
                    recyclerAdapter.setLocked(locked);
                    return true;
                case R.id.refresh_item:
                    List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
                    if (feeds != null) {
                        DBTasks.refreshAllFeeds(getActivity(), feeds);
                    }
                    return true;
                case R.id.clear_queue:
                    // make sure the user really wants to clear the queue
                    ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
                            R.string.clear_queue_label,
                            R.string.clear_queue_confirmation_msg) {

                        @Override
                        public void onConfirmButtonPressed(
                                DialogInterface dialog) {
                            dialog.dismiss();
                            DBWriter.clearQueue();
                        }
                    };
                    conDialog.createNewDialog().show();
                    return true;
                case R.id.queue_sort_alpha_asc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.ALPHA_ASC, true);
                    return true;
                case R.id.queue_sort_alpha_desc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.ALPHA_DESC, true);
                    return true;
                case R.id.queue_sort_date_asc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.DATE_ASC, true);
                    return true;
                case R.id.queue_sort_date_desc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.DATE_DESC, true);
                    return true;
                case R.id.queue_sort_duration_asc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.DURATION_ASC, true);
                    return true;
                case R.id.queue_sort_duration_desc:
                    QueueSorter.sort(getActivity(), QueueSorter.Rule.DURATION_DESC, true);
                    return true;
                default:
                    return false;
            }
        } else {
            return true;
        }
    }


    @Override
    public boolean onContextItemSelected(MenuItem item) {
        if(!isVisible()) {
            return false;
        }
        FeedItem selectedItem = recyclerAdapter.getSelectedItem();
        if (selectedItem == null) {
            Log.i(TAG, "Selected item was null, ignoring selection");
            return super.onContextItemSelected(item);
        }

        try {
            return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
        } catch (DownloadRequestException e) {
            e.printStackTrace();
            Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
            return true;
        }
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.queue_label);

        View root = inflater.inflate(R.layout.queue_fragment, container, false);
        infoBar = (TextView) root.findViewById(R.id.info_bar);
        recyclerView = (RecyclerView) root.findViewById(R.id.recyclerView);
        layoutManager = new LinearLayoutManager(getActivity());
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
        recyclerView.setHasFixedSize(true);
        registerForContextMenu(recyclerView);

        itemTouchHelper = new ItemTouchHelper(
            new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT) {

                @Override
                public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                    int from = viewHolder.getAdapterPosition();
                    int to = target.getAdapterPosition();
                    Log.d(TAG, "move(" + from + ", " + to + ")");
                    queue.add(to, queue.remove(from));
                    recyclerAdapter.notifyItemMoved(from, to);
                    DBWriter.moveQueueItem(from, to, true);
                    return true;
                }

                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    if(subscription != null) {
                        subscription.unsubscribe();
                    }
                    final int position = viewHolder.getAdapterPosition();
                    Log.d(TAG, "remove(" + position + ")");
                    final FeedItem item = queue.get(position);
                    final boolean isRead = item.isPlayed();
                    DBWriter.markItemPlayed(FeedItem.PLAYED, item.getId());
                    DBWriter.removeQueueItem(getActivity(), item, true);
                    Snackbar snackbar = Snackbar.make(root, getString(R.string.marked_as_read_label), Snackbar.LENGTH_LONG);
                    snackbar.setAction(getString(R.string.undo), v -> {
                        DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false);
                        if(false == isRead) {
                            DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
                        }
                    });
                    snackbar.show();
                }

                @Override
                public boolean isLongPressDragEnabled() {
                    return false == UserPreferences.isQueueLocked();
                }

                @Override
                public boolean isItemViewSwipeEnabled() {
                    return false == UserPreferences.isQueueLocked();
                }

                @Override
                public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
                                              int actionState) {
                    // We only want the active item
                    if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                        if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) {
                            QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder =
                                    (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder;
                            itemViewHolder.onItemSelected();
                        }
                    }

                    super.onSelectedChanged(viewHolder, actionState);
                }
                @Override
                public void clearView(RecyclerView recyclerView,
                                      RecyclerView.ViewHolder viewHolder) {
                    super.clearView(recyclerView, viewHolder);

                    if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) {
                        QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder =
                                (QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder;
                        itemViewHolder.onItemClear();
                    }
                }
            }
        );
        itemTouchHelper.attachToRecyclerView(recyclerView);

        txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
        txtvEmpty.setVisibility(View.GONE);
        progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
        progLoading.setVisibility(View.VISIBLE);

        return root;
    }

    private void onFragmentLoaded() {
        if (recyclerAdapter == null) {
            MainActivity activity = (MainActivity) getActivity();
            recyclerAdapter = new QueueRecyclerAdapter(activity, itemAccess,
                new DefaultActionButtonCallback(activity), itemTouchHelper);
            recyclerView.setAdapter(recyclerAdapter);
            downloadObserver = new DownloadObserver(activity, new Handler(), downloadObserverCallback);
            downloadObserver.onResume();
        }
        if(queue == null || queue.size() == 0) {
            recyclerView.setVisibility(View.GONE);
            txtvEmpty.setVisibility(View.VISIBLE);
        } else {
            txtvEmpty.setVisibility(View.GONE);
            recyclerView.setVisibility(View.VISIBLE);
        }

        restoreScrollPosition();

        // we need to refresh the options menu because it sometimes
        // needs data that may have just been loaded.
        getActivity().supportInvalidateOptionsMenu();

        refreshInfoBar();
    }

    private void refreshInfoBar() {
        String info = queue.size() + getString(R.string.episodes_suffix);
        if(queue.size() > 0) {
            long duration = 0;
            for(FeedItem item : queue) {
                if(item.getMedia() != null) {
                    duration += item.getMedia().getDuration();
                }
            }
            info += " \u2022 ";
            info += Converter.getDurationStringLocalized(getActivity(), duration);
        }
        infoBar.setText(info);
    }

    private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
        @Override
        public void onContentChanged(List<Downloader> downloaderList) {
            QueueFragment.this.downloaderList = downloaderList;
            if (recyclerAdapter != null && !blockDownloadObserverUpdate) {
                recyclerAdapter.notifyDataSetChanged();
            }
        }
    };

    private QueueRecyclerAdapter.ItemAccess itemAccess = new QueueRecyclerAdapter.ItemAccess() {
        @Override
        public int getCount() {
            return queue != null ? queue.size() : 0;
        }

        @Override
        public FeedItem getItem(int position) {
            return queue != null ? queue.get(position) : null;
        }

        @Override
        public long getItemDownloadedBytes(FeedItem item) {
            if (downloaderList != null) {
                for (Downloader downloader : downloaderList) {
                    if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
                            && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
                        Log.d(TAG, "downloaded bytes: " + downloader.getDownloadRequest().getSoFar());
                        return downloader.getDownloadRequest().getSoFar();
                    }
                }
            }
            return 0;
        }

        @Override
        public long getItemDownloadSize(FeedItem item) {
            if (downloaderList != null) {
                for (Downloader downloader : downloaderList) {
                    if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
                            && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
                        Log.d(TAG, "downloaded size: " + downloader.getDownloadRequest().getSize());
                        return downloader.getDownloadRequest().getSize();
                    }
                }
            }
            return 0;
        }
        @Override
        public int getItemDownloadProgressPercent(FeedItem item) {
            if (downloaderList != null) {
                for (Downloader downloader : downloaderList) {
                    if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
                            && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
                        return downloader.getDownloadRequest().getProgressPercent();
                    }
                }
            }
            return 0;
        }

        @Override
        public LongList getQueueIds() {
            return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0);
        }
    };

    private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
        @Override
        public void update(EventDistributor eventDistributor, Integer arg) {
            if ((arg & EVENTS) != 0) {
                loadItems();
                if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
                    getActivity().supportInvalidateOptionsMenu();
                }
            }
        }
    };

    private void loadItems() {
        if(subscription != null) {
            subscription.unsubscribe();
        }
        if (queue == null) {
            recyclerView.setVisibility(View.GONE);
            txtvEmpty.setVisibility(View.GONE);
            progLoading.setVisibility(View.VISIBLE);
        }
        subscription = Observable.defer(() -> Observable.just(DBReader.getQueue()))
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(items -> {
                    if(items != null) {
                        progLoading.setVisibility(View.GONE);
                        queue = items;
                        onFragmentLoaded();
                        if(recyclerAdapter != null) {
                            recyclerAdapter.notifyDataSetChanged();
                        }
                    }
                }, error -> {
                    Log.e(TAG, Log.getStackTraceString(error));
                });
    }

}

perhaps I'm daft, how do we handle this case now?