com.vuze.android.remote.fragment.TorrentListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.android.remote.fragment.TorrentListFragment.java

Source

/**
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package com.vuze.android.remote.fragment;

import static com.vuze.android.remote.adapter.SideFilterAdapter.SideFilterInfo;

import java.util.*;

import android.animation.LayoutTransition;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.*;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.view.ActionMode.Callback;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.SubMenuBuilder;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.*;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;

import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import com.vuze.android.FlexibleRecyclerSelectionListener;
import com.vuze.android.FlexibleRecyclerView;
import com.vuze.android.MenuDialogHelper;
import com.vuze.android.remote.*;
import com.vuze.android.remote.AndroidUtils.ValueStringArray;
import com.vuze.android.remote.SessionInfo.RpcExecuter;
import com.vuze.android.remote.activity.TorrentViewActivity;
import com.vuze.android.remote.adapter.*;
import com.vuze.android.remote.dialog.*;
import com.vuze.android.remote.dialog.DialogFragmentFilterBy.FilterByDialogListener;
import com.vuze.android.remote.dialog.DialogFragmentSortBy.SortByDialogListener;
import com.vuze.android.remote.adapter.TorrentListAdapter.TorrentFilter;
import com.vuze.android.remote.rpc.*;
import com.vuze.android.remote.spanbubbles.SpanTags;
import com.vuze.android.widget.PreCachingLayoutManager;
import com.vuze.android.widget.SwipeRefreshLayoutExtra;
import com.vuze.util.MapUtils;

/**
 * Handles a ListView that shows Torrents
 */
public class TorrentListFragment extends Fragment
        implements TorrentListReceivedListener, FilterByDialogListener, SortByDialogListener, SessionInfoListener,
        ActionModeBeingReplacedListener, TagListReceivedListener, Animation.AnimationListener, View.OnKeyListener,
        SessionSettingsChangedListener, TorrentListRefreshingListener, NetworkState.NetworkStateListener

{
    private static final boolean DEBUG = AndroidUtils.DEBUG;

    private static final String TAG = "TorrentList";

    public final static String LETTERS_BS = "\u232B";

    public final static String LETTERS_NUMBERS = "0-9";

    public final static String LETTERS_PUNCTUATION = "Punctuation";

    public final static String LETTERS_NON = "Other";

    public static final int SIDELIST_MAX_WIDTH = VuzeRemoteApp.getContext().getResources()
            .getDimensionPixelSize(R.dimen.sidelist_max_width);

    private static final int SIDELIST_MIN_WIDTH = VuzeRemoteApp.getContext().getResources()
            .getDimensionPixelSize(R.dimen.sidelist_min_width);

    public static final int SIDELIST_DURATION_MS = 300;

    // Shrink sidelist, typically for 7" Tablets in Portrait
    private static final int SIDELIST_COLLAPSE_UNTIL_WIDTH_PX = AndroidUtilsUI.dpToPx(500);

    // Sidelist always full-width, typically for 9"-11" Tablets, 7" Tablets in
    // Landscape, and TVs
    private static final int SIDELIST_KEEP_EXPANDED_AT_PX = AndroidUtilsUI.dpToPx(610);

    // Rare case when there's not enough height.  Show only active sidelist
    // header
    // This would be for Dell Streak (800x480dp) if it was API >= 13
    // Can't be >= 540, since TVs are that.
    // Each row is 42dp.  42x4=168, plus top actionbar (64dp?) and our header
    // (20dp?) ~ 252 dp.  Want to show at least 6 rows of the list.  6x42=252
    private static final int SIDELIST_HIDE_UNSELECTED_HEADERS_MAX_PX = AndroidUtilsUI.dpToPx(500);

    public interface OnTorrentSelectedListener extends ActionModeBeingReplacedListener {
        void onTorrentSelectedListener(TorrentListFragment torrentListFragment, long[] ids, boolean inMultiMode);
    }

    private RecyclerView listview;

    protected ActionMode mActionMode;

    private TorrentListAdapter torrentListAdapter;

    private SessionInfo sessionInfo;

    private EditText filterEditText;

    private Callback mActionModeCallbackV7;

    private TextView tvFilteringBy;

    private TextView tvTorrentCount;

    private boolean actionModeBeingReplaced;

    private boolean rebuildActionMode;

    private Toolbar tb;

    private OnTorrentSelectedListener mCallback;

    // >> SideList.. could probably some things in a Map

    private LinearLayout sideListArea;

    private TextView tvSideFilterText;

    private RecyclerView listSideActions;

    private RecyclerView listSideSort;

    private RecyclerView listSideTags;

    private RecyclerView listSideFilter;

    private SideActionsAdapter sideActionsAdapter;

    private SideSortAdapter sideSortAdapter;

    private SideTagAdapter sideTagAdapter;

    private SideFilterAdapter sideFilterAdapter;

    private Boolean sidelistIsExpanded = null;

    private ViewGroup sidebarViewActive = null;

    private Boolean sidelistInFocus = null;

    private boolean hideUnselectedSideHeaders = false;

    private List<ViewGroup> listHeaderViewGroups = new ArrayList<>();
    // << SideList

    private View fragView;

    private Boolean isSmall;

    @Override
    public void onAttach(Context activity) {
        super.onAttach(activity);

        if (activity instanceof OnTorrentSelectedListener) {
            mCallback = (OnTorrentSelectedListener) activity;
        }

        FlexibleRecyclerSelectionListener rs = new FlexibleRecyclerSelectionListener<TorrentListAdapter, Long>() {
            @Override
            public void onItemSelected(TorrentListAdapter adapter, final int position, boolean isChecked) {
            }

            @Override
            public void onItemClick(TorrentListAdapter adapter, int position) {
            }

            @Override
            public boolean onItemLongClick(TorrentListAdapter adapter, int position) {
                if (AndroidUtils.usesNavigationControl()) {
                    return showTorrentContextMenu();
                }
                return false;
            }

            @Override
            public void onItemCheckedChanged(TorrentListAdapter adapter, Long item, boolean isChecked) {
                if (mActionMode == null && isChecked) {
                    showContextualActions(false);
                }

                if (adapter.getCheckedItemCount() == 0) {
                    finishActionMode();
                }

                if (adapter.isMultiCheckMode()) {
                    updateActionModeText(mActionMode);
                }
                updateCheckedIDs();

                AndroidUtils.invalidateOptionsMenuHC(getActivity(), mActionMode);
            }
        };

        torrentListAdapter = new TorrentListAdapter(activity, rs) {
            @Override
            public void lettersUpdated(HashMap<String, Integer> mapLetters) {
                if (sideFilterAdapter != null) {
                    if (AndroidUtils.DEBUG_ADAPTER) {
                        Log.d(TAG, "lettersUpdated: ");
                    }
                    String[] keys = mapLetters.keySet().toArray(new String[mapLetters.size()]);
                    Arrays.sort(keys, new Comparator<String>() {
                        @Override
                        public int compare(String lhs, String rhs) {
                            int rsh_length = rhs.length();
                            if ((rsh_length > 1) == (lhs.length() > 1)) {
                                return lhs.compareTo(rhs);
                            }
                            return rsh_length > 1 ? -1 : 1;
                        }
                    });
                    final ArrayList<SideFilterAdapter.SideFilterInfo> list = new ArrayList<>();
                    for (String c : keys) {
                        Integer count = mapLetters.get(c);
                        SideFilterInfo info = new SideFilterInfo(c, count);
                        list.add(info);
                    }
                    TorrentFilter filter = torrentListAdapter.getFilter();
                    if (tvSideFilterText.getText().length() > 0 || !filter.getCompactDigits()
                            || !filter.getCompactNonLetters() || !filter.getCompactPunctuation()) {
                        list.add(0, new SideFilterInfo(LETTERS_BS, 0));
                    }

                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            FragmentActivity activity = getActivity();
                            if (activity == null) {
                                return;
                            }
                            boolean hadFocus = isChildOf(activity.getCurrentFocus(), listSideFilter);
                            sideFilterAdapter.setItems(list);
                            if (hadFocus) {
                                listSideFilter.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        listSideFilter.requestFocus();
                                    }
                                });
                            }
                        }
                    });
                }
            }
        };
        if (sideFilterAdapter != null) {
            torrentListAdapter.getFilter().setBuildLetters(true);
        }
        torrentListAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                updateTorrentCount(torrentListAdapter.getItemCount());
            }

            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                updateTorrentCount(torrentListAdapter.getItemCount());
            }

            @Override
            public void onChanged() {
                updateTorrentCount(torrentListAdapter.getItemCount());
            }
        });
        torrentListAdapter.setMultiCheckModeAllowed(!AndroidUtils.usesNavigationControl());
    }

    @Override
    public void uiReady(TransmissionRPC rpc) {
        if (getActivity() == null) {
            return;
        }

        RemoteProfile remoteProfile = sessionInfo.getRemoteProfile();

        String[] sortBy = remoteProfile.getSortBy();
        Boolean[] sortOrder = remoteProfile.getSortOrder();
        if (sortBy != null) {
            int which = TorrentUtils.findSordIdFromTorrentFields(getContext(), sortBy);
            sortBy(sortBy, sortOrder, which, false);
        }

        long filterBy = remoteProfile.getFilterBy();
        // Convert All Filter to tag if we have tags
        if (filterBy == TorrentListAdapter.FILTERBY_ALL && sessionInfo.getSupportsTags()) {
            Long tagAllUID = sessionInfo.getTagAllUID();
            if (tagAllUID != null) {
                filterBy = tagAllUID;
            }
        }
        if (filterBy > 10) {
            Map<?, ?> tag = sessionInfo.getTag(filterBy);

            filterBy(filterBy, MapUtils.getMapString(tag, "name", "fooo"), false);
        } else if (filterBy >= 0) {
            final ValueStringArray filterByList = AndroidUtils.getValueStringArray(getResources(),
                    R.array.filterby_list);
            for (int i = 0; i < filterByList.values.length; i++) {
                long val = filterByList.values[i];
                if (val == filterBy) {
                    filterBy(filterBy, filterByList.strings[i], false);
                    break;
                }
            }
        }

    }

    @Override
    public void transmissionRpcAvailable(SessionInfo sessionInfo) {
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.Fragment#onCreateView(android.view
     * .LayoutInflater, android.view.ViewGroup, android.os.Bundle)
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        fragView = inflater.inflate(R.layout.frag_torrent_list, container, false);

        setupActionModeCallback();

        final SwipeRefreshLayoutExtra swipeRefresh = (SwipeRefreshLayoutExtra) fragView
                .findViewById(R.id.swipe_container);
        if (swipeRefresh != null) {
            swipeRefresh.setExtraLayout(R.layout.swipe_layout_extra);

            LastUpdatedInfo lui = getLastUpdatedString();
            if (lui != null) {
                View extraView = swipeRefresh.getExtraView();
                if (extraView != null) {
                    TextView tvSwipeText = (TextView) extraView.findViewById(R.id.swipe_text);
                    tvSwipeText.setText(lui.s);
                }
            }
            swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    if (sessionInfo == null) {
                        return;
                    }
                    sessionInfo.addTorrentListReceivedListener(new TorrentListReceivedListener() {

                        @Override
                        public void rpcTorrentListReceived(String callID, List<?> addedTorrentMaps,
                                List<?> removedTorrentIDs) {
                            AndroidUtils.runOnUIThread(TorrentListFragment.this, new Runnable() {
                                @Override
                                public void run() {
                                    if (getActivity() == null) {
                                        return;
                                    }
                                    swipeRefresh.setRefreshing(false);
                                    LastUpdatedInfo lui = getLastUpdatedString();
                                    View extraView = swipeRefresh.getExtraView();
                                    if (extraView != null) {
                                        TextView tvSwipeText = (TextView) extraView.findViewById(R.id.swipe_text);
                                        tvSwipeText.setText(lui.s);
                                    }
                                }
                            });
                            sessionInfo.removeTorrentListReceivedListener(this);
                        }
                    }, false);
                    sessionInfo.triggerRefresh(true);
                }
            });
            swipeRefresh.setOnExtraViewVisibilityChange(
                    new SwipeRefreshLayoutExtra.OnExtraViewVisibilityChangeListener() {
                        private Handler pullRefreshHandler;

                        @Override
                        public void onExtraViewVisibilityChange(final View view, int visibility) {
                            if (pullRefreshHandler != null) {
                                pullRefreshHandler.removeCallbacksAndMessages(null);
                                pullRefreshHandler = null;
                            }
                            if (visibility != View.VISIBLE) {
                                return;
                            }

                            pullRefreshHandler = new Handler(Looper.getMainLooper());

                            pullRefreshHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    if (getActivity() == null) {
                                        return;
                                    }
                                    LastUpdatedInfo lui = getLastUpdatedString();
                                    if (lui == null) {
                                        return;
                                    }
                                    TextView tvSwipeText = (TextView) view.findViewById(R.id.swipe_text);
                                    tvSwipeText.setText(lui.s);

                                    if (pullRefreshHandler != null) {
                                        pullRefreshHandler.postDelayed(this,
                                                lui.sinceMS < DateUtils.MINUTE_IN_MILLIS
                                                        ? DateUtils.SECOND_IN_MILLIS
                                                        : lui.sinceMS < DateUtils.HOUR_IN_MILLIS
                                                                ? DateUtils.MINUTE_IN_MILLIS
                                                                : DateUtils.HOUR_IN_MILLIS);
                                    }
                                }
                            }, 0);
                        }
                    });
        }

        listview = (RecyclerView) fragView.findViewById(R.id.listTorrents);
        listview.setLayoutManager(new PreCachingLayoutManager(getContext()));
        listview.setAdapter(torrentListAdapter);

        if (AndroidUtils.isTV()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                listview.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
            }
            ((FastScrollRecyclerView) listview).setEnableFastScrolling(false);
            ((FlexibleRecyclerView) listview).setFixedVerticalHeight(AndroidUtilsUI.dpToPx(48));
            listview.setVerticalFadingEdgeEnabled(true);
            listview.setFadingEdgeLength(AndroidUtilsUI.dpToPx((int) (48 * 1.5)));
        }

        filterEditText = (EditText) fragView.findViewById(R.id.filterText);
        filterEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Filter filter = torrentListAdapter.getFilter();
                filter.filter(s);
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        setupSideListArea(fragView);

        /** Handy code to watch the states of row 2
         listview.postDelayed(new Runnable() {
         String oldS = "";
            
         @Override public void run() {
            
         String s = (listview.getChildCount() < 3 ? ""
         : AndroidUtils.getStatesString(listview.getChildAt(2).getDrawableState
         ()));
            
         if (!s.equals(oldS)) {
         oldS = s;
         Log.e(TAG, "States of 2: " + s);
         }
            
         listview.postDelayed(this, 500);
         }
         }, 500);
         */

        setHasOptionsMenu(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            onCreateViewHC(fragView);
        }

        if (sideListArea != null) {
            fragView.post(new Runnable() {
                @Override
                public void run() {
                    FragmentActivity activity = getActivity();
                    if (activity == null) {
                        return;
                    }
                    Window window = activity.getWindow();
                    if (window == null) {
                        return;
                    }
                    int dpHeight = window.getDecorView().getHeight();
                    hideUnselectedSideHeaders = dpHeight < SIDELIST_HIDE_UNSELECTED_HEADERS_MAX_PX;
                    expandSideListWidth(sidelistInFocus);
                    if (AndroidUtils.DEBUG) {
                        Log.d(TAG, "onAttach: hide? " + hideUnselectedSideHeaders + ";" + dpHeight);
                    }
                }
            });
        }

        return fragView;

    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void onCreateViewHC(View fragView) {
        if (sideListArea != null) {
            fragView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                int lastWidth = -1;

                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
                        int oldTop, int oldRight, int oldBottom) {
                    int width = right - left;
                    if (width != lastWidth) {
                        lastWidth = width;
                        expandSideListWidth(sidelistInFocus);
                    }
                }
            });
        }
    }

    private void setupSideListArea(View view) {
        sideListArea = (LinearLayout) view.findViewById(R.id.sidelist_layout);
        if (sideListArea == null) {
            return;
        }

        FragmentActivity activity = getActivity();
        if (activity instanceof TorrentViewActivity) {
            ((TorrentViewActivity) activity).setBottomToolbarEnabled(false);
            ((TorrentViewActivity) activity).setShowGlobalActionBar(false);
        }

        if (!AndroidUtils.hasTouchScreen()) {
            // Switch SideList width based on focus.  For touch screens, we use
            // touch events.  For non-touch screens (TV) we watch for focus changes
            ViewTreeObserver vto = sideListArea.getViewTreeObserver();
            vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {

                @Override
                public void onGlobalFocusChanged(View oldFocus, View newFocus) {

                    boolean isChildOfSideList = isChildOf(newFocus, sideListArea);
                    boolean isHeader = childOrParentHasTag(newFocus, "sideheader");
                    if ((sidelistIsExpanded == null || sidelistIsExpanded) && !isChildOfSideList) {
                        //left focus
                        sidelistInFocus = false;
                        expandSideListWidth(false);
                    } else if ((sidelistIsExpanded == null || !sidelistIsExpanded) && isHeader
                            && newFocus != listSideFilter) {
                        sidelistInFocus = true;
                        expandSideListWidth(true);
                    }
                }
            });
        }

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            LayoutTransition layoutTransition = new LayoutTransition();
            layoutTransition.setDuration(300);
            sideListArea.setLayoutTransition(layoutTransition);
        }

        // Could have used a ExpandableListView.. oh well
        setupExpando(view, R.id.sidesort_header, R.id.sidesort_list);
        setupExpando(view, R.id.sidetag_header, R.id.sidetag_list);
        setupExpando(view, R.id.sidefilter_header, R.id.sidefilter_list);
        setupExpando(view, R.id.sideactions_header, R.id.sideactions_list);

        setupSideFilter(view);
        setupSideTags(view);
        setupSideSort(view);
        setupSideActions(view);
    }

    private boolean canSideListExpand() {
        int width = fragView.getWidth();
        boolean noExpanding = width < SIDELIST_COLLAPSE_UNTIL_WIDTH_PX;
        return !noExpanding;
    }

    private boolean expandSideListWidth(Boolean expand) {
        int width = fragView.getWidth();
        boolean noExpanding = width < SIDELIST_COLLAPSE_UNTIL_WIDTH_PX;
        // We have a Motorola Xoom on Android 4.0.4 that can't handle shrinking
        // (torrent list view overlays)
        boolean noShrinking = width >= SIDELIST_KEEP_EXPANDED_AT_PX
                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN;

        if (expand == null) {
            if (noExpanding && noShrinking) {
                return false;
            }
            expand = noShrinking;
        }

        if (sidelistIsExpanded != null) {
            // before listening to caller, do our checks
            if (sidelistIsExpanded && noExpanding && !noShrinking) {
                expand = false;
            }
            if (!sidelistIsExpanded && noShrinking && !noExpanding) {
                expand = true;
            }
        }

        if (expand && noExpanding && !noShrinking) {
            expand = false;
        }
        if (!expand && noShrinking && !noExpanding) {
            expand = true;
        }

        if (sidelistIsExpanded != null && expand == sidelistIsExpanded) {
            return false;
        }

        if (expand) {
            sizeTo(sideListArea, SIDELIST_MAX_WIDTH, SIDELIST_DURATION_MS, this);
        } else {
            sizeTo(sideListArea, SIDELIST_MIN_WIDTH, SIDELIST_DURATION_MS, this);
        }
        sidelistIsExpanded = expand;
        return true;
    }

    private void setupSideActions(View view) {
        listSideActions = (RecyclerView) view.findViewById(R.id.sideactions_list);
        if (listSideActions == null) {
            return;
        }

        listSideActions.setLayoutManager(new PreCachingLayoutManager(getContext()));

        sideActionsAdapter = new SideActionsAdapter(getContext(), sessionInfo,
                new FlexibleRecyclerSelectionListener<SideActionsAdapter, SideActionsAdapter.SideActionsInfo>() {
                    @Override
                    public void onItemClick(SideActionsAdapter adapter, int position) {
                        SideActionsAdapter.SideActionsInfo item = adapter.getItem(position);
                        if (item == null) {
                            return;
                        }
                        if (getActivity().onOptionsItemSelected(item.menuItem)) {
                            return;
                        }
                        int itemId = item.menuItem.getItemId();
                        if (itemId == R.id.action_social) {
                            MenuBuilder menuBuilder = new MenuBuilder(getContext());
                            MenuInflater menuInflater = getActivity().getMenuInflater();
                            menuInflater.inflate(R.menu.menu_torrent_list, menuBuilder);
                            getActivity().onPrepareOptionsMenu(menuBuilder);
                            MenuItem itemSocial = menuBuilder.findItem(R.id.action_social);
                            if (itemSocial != null) {
                                SubMenu subMenu = itemSocial.getSubMenu();
                                if (subMenu instanceof SubMenuBuilder) {
                                    ((SubMenuBuilder) subMenu).setCallback(new MenuBuilder.Callback() {
                                        @Override
                                        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
                                            return getActivity().onOptionsItemSelected(item);
                                        }

                                        @Override
                                        public void onMenuModeChange(MenuBuilder menu) {

                                        }
                                    });
                                    MenuDialogHelper menuDialogHelper = new MenuDialogHelper(
                                            (SubMenuBuilder) subMenu);
                                    menuDialogHelper.show(null);
                                }
                            }
                        }
                    }

                    @Override
                    public boolean onItemLongClick(SideActionsAdapter adapter, int position) {
                        return false;
                    }

                    @Override
                    public void onItemSelected(SideActionsAdapter adapter, int position, boolean isChecked) {

                    }

                    @Override
                    public void onItemCheckedChanged(SideActionsAdapter adapter,
                            SideActionsAdapter.SideActionsInfo item, boolean isChecked) {

                    }
                });
        listSideActions.setAdapter(sideActionsAdapter);
    }

    private void setupSideSort(View view) {
        listSideSort = (RecyclerView) view.findViewById(R.id.sidesort_list);
        if (listSideSort == null) {
            return;
        }

        listSideSort.setLayoutManager(new PreCachingLayoutManager(getContext()));

        sideSortAdapter = new SideSortAdapter(getContext(),
                new FlexibleRecyclerSelectionListener<SideSortAdapter, SideSortAdapter.SideSortInfo>() {
                    @Override
                    public void onItemClick(SideSortAdapter adapter, int position) {
                    }

                    @Override
                    public boolean onItemLongClick(SideSortAdapter adapter, int position) {
                        return false;
                    }

                    @Override
                    public void onItemSelected(SideSortAdapter adapter, int position, boolean isChecked) {

                    }

                    @Override
                    public void onItemCheckedChanged(SideSortAdapter adapter, SideSortAdapter.SideSortInfo item,
                            boolean isChecked) {

                        if (!isChecked) {
                            return;
                        }
                        adapter.setItemChecked(item, false);

                        SortByFields sortByFields = TorrentUtils.getSortByFields(getContext())[((int) item.id)];
                        if (sortByFields != null) {
                            if (item.id == adapter.getCurrentSort()) {
                                flipSortOrder();
                            } else {
                                sortBy(sortByFields.sortFieldIDs, sortByFields.sortOrderAsc, (int) item.id, true);
                            }
                        }

                    }
                });
        listSideSort.setAdapter(sideSortAdapter);
    }

    private void setupSideTags(View view) {
        listSideTags = (RecyclerView) view.findViewById(R.id.sidetag_list);
        if (listSideTags == null) {
            return;
        }

        listSideTags.setLayoutManager(new PreCachingLayoutManager(getContext()));

        if (getActivity() instanceof SessionInfoGetter) {
            SessionInfoGetter getter = (SessionInfoGetter) getActivity();
            sessionInfo = getter.getSessionInfo();
            sideTagAdapter = new SideTagAdapter(getContext(), sessionInfo,
                    new FlexibleRecyclerSelectionListener<SideTagAdapter, SideTagAdapter.SideTagInfo>() {
                        @Override
                        public void onItemClick(SideTagAdapter adapter, int position) {
                        }

                        @Override
                        public boolean onItemLongClick(SideTagAdapter adapter, int position) {
                            return false;
                        }

                        @Override
                        public void onItemSelected(SideTagAdapter adapter, int position, boolean isChecked) {
                        }

                        @Override
                        public void onItemCheckedChanged(SideTagAdapter adapter, SideTagAdapter.SideTagInfo item,
                                boolean isChecked) {

                            if (!isChecked) {
                                return;
                            }
                            adapter.setItemChecked(item, false);

                            filterBy(item.id, MapUtils.getMapString(item.tag, "name", ""), true);
                        }
                    });

            listSideTags.setAdapter(sideTagAdapter);

            if (!sessionInfo.getSupportsTags()) {
                // TRANSMISSION
                ValueStringArray filterByList = AndroidUtils.getValueStringArray(getResources(),
                        R.array.filterby_list);

                for (int i = 0; i < filterByList.strings.length; i++) {
                    long id = filterByList.values[i];
                    Map map = new HashMap();
                    map.put("uid", id);
                    SideTagAdapter.SideTagInfo sideTagInfo = new SideTagAdapter.SideTagInfo(map);
                    sideTagAdapter.addItem(sideTagInfo);
                }
            }
        }
    }

    private void setupSideFilter(View view) {
        listSideFilter = (RecyclerView) view.findViewById(R.id.sidefilter_list);
        if (listSideFilter == null) {
            return;
        }
        if (torrentListAdapter != null) {
            torrentListAdapter.getFilter().setBuildLetters(true);
        }

        tvSideFilterText = (TextView) view.findViewById(R.id.sidefilter_text);

        tvSideFilterText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Filter filter = torrentListAdapter.getFilter();
                filter.filter(s);
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        listSideFilter.setLayoutManager(new PreCachingLayoutManager(getContext()));

        sideFilterAdapter = new SideFilterAdapter(getContext(),
                new FlexibleRecyclerSelectionListener<SideFilterAdapter, SideFilterAdapter.SideFilterInfo>() {
                    @Override
                    public void onItemCheckedChanged(SideFilterAdapter adapter,
                            SideFilterAdapter.SideFilterInfo item, boolean isChecked) {
                        if (!isChecked) {
                            return;
                        }
                        adapter.setItemChecked(item, false);

                        String s = item.letters;
                        if (s.equals(LETTERS_NUMBERS)) {
                            TorrentFilter filter = torrentListAdapter.getFilter();
                            filter.setCompactDigits(false);
                            filter.refilter();
                            return;
                        }
                        if (s.equals(LETTERS_NON)) {
                            TorrentFilter filter = torrentListAdapter.getFilter();
                            filter.setCompactOther(false);
                            filter.refilter();
                            return;
                        }
                        if (s.equals(LETTERS_PUNCTUATION)) {
                            TorrentFilter filter = torrentListAdapter.getFilter();
                            filter.setCompactPunctuation(false);
                            filter.refilter();
                            return;
                        }
                        if (s.equals(LETTERS_BS)) {
                            CharSequence text = tvSideFilterText.getText();
                            if (text.length() > 0) {
                                text = text.subSequence(0, text.length() - 1);
                                tvSideFilterText.setText(text);
                            } else {
                                TorrentFilter filter = torrentListAdapter.getFilter();
                                filter.setCompactPunctuation(true);
                                filter.setCompactDigits(true);
                                filter.setCompactOther(true);
                                filter.refilter();
                            }
                            return;
                        }
                        s = tvSideFilterText.getText() + s;
                        tvSideFilterText.setText(s);
                    }

                    @Override
                    public boolean onItemLongClick(SideFilterAdapter adapter, int position) {
                        return false;
                    }

                    @Override
                    public void onItemSelected(SideFilterAdapter adapter, int position, boolean isChecked) {

                    }

                    @Override
                    public void onItemClick(SideFilterAdapter adapter, int position) {

                    }
                });
        listSideFilter.setAdapter(sideFilterAdapter);

    }

    private void setupExpando(View view, int id_header, int id_body) {
        ViewGroup vgHeader = (ViewGroup) view.findViewById(id_header);
        final ViewGroup vgBody = (ViewGroup) view.findViewById(id_body);
        if (vgBody == null || vgHeader == null) {
            return;
        }
        listHeaderViewGroups.add(vgHeader);
        vgHeader.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                sidelistInFocus = true;
                // Consume touch event if user clicked the active sidelist view to
                // expand it
                // Otherwise, the active sidelist content would be collaped
                return expandSideListWidth(true) && sidebarViewActive == vgBody;
            }
        });
        vgHeader.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!sidelistIsExpanded && canSideListExpand()) {
                    return;
                }
                boolean same = sidebarViewActive == vgBody;
                if (sidebarViewActive != null) {
                    sidebarViewActive.setVisibility(View.GONE);
                }
                if (same) {
                    sidebarViewActive = null;
                    if (hideUnselectedSideHeaders) {
                        for (ViewGroup vgHeader : listHeaderViewGroups) {
                            vgHeader.setVisibility(View.VISIBLE);
                        }
                    }
                } else {
                    vgBody.setVisibility(View.VISIBLE);
                    sidebarViewActive = vgBody;

                    if (hideUnselectedSideHeaders) {
                        for (ViewGroup vgHeader : listHeaderViewGroups) {
                            vgHeader.setVisibility(vgHeader == v ? View.VISIBLE : View.GONE);
                        }
                    }
                }

                if (tvSideFilterText != null && listSideFilter != null) {
                    tvSideFilterText.setVisibility(tvSideFilterText.getText().length() == 0
                            && ((View) listSideFilter).getVisibility() == View.GONE ? View.GONE : View.VISIBLE);
                }

            }
        });
    }

    public static boolean childOrParentHasTag(View child, String tag) {
        if (child == null || tag == null) {
            return false;
        }
        if (tag.equals(child.getTag())) {
            return true;
        }
        ViewParent parent = child.getParent();
        while (parent != null) {

            if ((parent instanceof View) && tag.equals(((View) parent).getTag())) {
                return true;
            }
            parent = parent.getParent();
        }
        return false;

    }

    public static boolean isChildOf(View child, ViewGroup vg) {
        if (child == null || vg == null) {
            return false;
        }
        ViewParent parent = child.getParent();
        while (parent != null) {
            if (parent == vg) {
                return true;
            }
            parent = parent.getParent();
        }
        return false;
    }

    public static void sizeTo(final View v, int finalWidth, int durationMS, Animation.AnimationListener listener) {
        final int initalWidth = v.getMeasuredWidth();

        final int diff = finalWidth - initalWidth;

        final int multiplier = diff < 0 ? -1 : 0;

        Animation a = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                v.getLayoutParams().width = initalWidth + ((int) (diff * interpolatedTime));
                v.requestLayout();
            }

            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };

        a.setAnimationListener(listener);

        if (durationMS < 0) {
            // 1dp/ms
            a.setDuration((int) ((diff * multiplier) / v.getContext().getResources().getDisplayMetrics().density));
        } else {
            a.setDuration(durationMS);
        }
        v.startAnimation(a);
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        if (sideActionsAdapter != null) {
            sideActionsAdapter.notifyDataSetChanged();
        }
        if (sideSortAdapter != null) {
            sideSortAdapter.notifyDataSetInvalidated();
        }
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (event.getAction() != KeyEvent.ACTION_UP) {
            return false;
        }
        switch (keyCode) {
        // NOTE:
        // KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_MENU);
        case KeyEvent.KEYCODE_MENU:
        case KeyEvent.KEYCODE_BUTTON_X:
        case KeyEvent.KEYCODE_INFO: {
            if (tb == null) {
                return showTorrentContextMenu();
            }
            break;
        }

        }
        return false;
    }

    private boolean showTorrentContextMenu() {
        int selectedPosition = torrentListAdapter.getSelectedPosition();
        if (selectedPosition < 0) {
            return false;
        }
        String s;
        int checkedItemCount = torrentListAdapter.getCheckedItemCount();
        if (checkedItemCount <= 1) {
            Map<?, ?> item = torrentListAdapter.getTorrentItem(selectedPosition);
            s = getResources().getString(R.string.torrent_actions_for, MapUtils.getMapString(item, "name", "???"));
        } else {
            s = getResources().getQuantityString(R.plurals.torrent_actions_for_multiple, checkedItemCount,
                    checkedItemCount);
        }

        return AndroidUtilsUI.popupContextMenu(getContext(), this, s);
    }

    @Override
    public void sessionSettingsChanged(SessionSettings newSessionSettings) {
        boolean isSmallNew = sessionInfo.getRemoteProfile().useSmallLists();
        if (isSmall != null && isSmallNew != isSmall) {
            torrentListAdapter.setViewType(isSmallNew ? 1 : 0);
        }
        isSmall = isSmallNew;

        if (sideActionsAdapter != null) {
            sideActionsAdapter.updateMenuItems();
        }
    }

    @Override
    public void speedChanged(long downloadSpeed, long uploadSpeed) {

    }

    @Override
    public void rpcTorrentListRefreshingChanged(boolean refreshing) {
        AndroidUtils.runOnUIThread(this, new AndroidUtils.RunnableWithActivity() {
            @Override
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                if (sideActionsAdapter != null) {
                    sideActionsAdapter.updateRefreshButton();
                }
            }
        });
    }

    @Override
    public void onlineStateChanged(boolean isOnline, boolean isOnlineMobile) {
        if (sideActionsAdapter == null) {
            return;
        }
        AndroidUtils.runOnUIThread(this, new AndroidUtils.RunnableWithActivity() {
            @Override
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                if (sideActionsAdapter != null) {
                    sideActionsAdapter.updateMenuItems();
                }
            }
        });
    }

    private class LastUpdatedInfo {
        long sinceMS;

        String s;

        public LastUpdatedInfo(long sinceMS, String s) {
            this.sinceMS = sinceMS;
            this.s = s;
        }
    }

    private LastUpdatedInfo getLastUpdatedString() {
        FragmentActivity activity = getActivity();
        if (activity == null) {
            return null;
        }
        long lastUpdated = sessionInfo == null ? 0 : sessionInfo.getLastTorrentListReceivedOn();
        long sinceMS = System.currentTimeMillis() - lastUpdated;
        String since = DateUtils.getRelativeDateTimeString(activity, lastUpdated, DateUtils.SECOND_IN_MILLIS,
                DateUtils.WEEK_IN_MILLIS, 0).toString();
        String s = activity.getResources().getString(R.string.last_updated, since);

        return new LastUpdatedInfo(sinceMS, s);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (DEBUG) {
            Log.d(TAG, "onSaveInstanceState");
        }
        if (torrentListAdapter != null) {
            torrentListAdapter.onSaveInstanceState(outState);
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onViewStateRestored(Bundle savedInstanceState) {
        if (DEBUG) {
            Log.d(TAG, "onViewStateRestored");
        }
        super.onViewStateRestored(savedInstanceState);
        if (torrentListAdapter != null) {
            torrentListAdapter.onRestoreInstanceState(savedInstanceState, listview);
        }
        if (listview != null) {
            updateCheckedIDs();
        }
    }

    @Override
    public void onStart() {
        if (DEBUG) {
            Log.d(TAG, "onStart");
        }
        super.onStart();
        VuzeEasyTracker.getInstance(this).fragmentStart(this, TAG);
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.Fragment#onResume()
     */
    @Override
    public void onResume() {
        if (DEBUG) {
            Log.d(TAG, "onResume");
        }
        super.onResume();

        VuzeRemoteApp.getNetworkState().addListener(this);

        if (getActivity() instanceof SessionInfoGetter) {
            SessionInfoGetter getter = (SessionInfoGetter) getActivity();
            sessionInfo = getter.getSessionInfo();
            torrentListAdapter.setSessionInfo(sessionInfo);
            sessionInfo.addTorrentListReceivedListener(TAG, this);
            if (listSideTags != null) {
                sessionInfo.addTagListReceivedListener(this);
            }
            sessionInfo.addRpcAvailableListener(this);
            sessionInfo.addSessionSettingsChangedListeners(this);
            sessionInfo.addTorrentListRefreshingListener(this, false);
        }
        if (torrentListAdapter != null) {
            if (filterEditText != null && filterEditText.length() > 0) {
                torrentListAdapter.getFilter().filter(filterEditText.getText());
            }
            if (tvSideFilterText != null && tvSideFilterText.length() > 0) {
                torrentListAdapter.getFilter().filter(tvSideFilterText.getText());
            }
        }
    }

    /* (non-Javadoc)
     * @see android.support.v4.app.Fragment#onPause()
     */
    @Override
    public void onPause() {
        VuzeRemoteApp.getNetworkState().removeListener(this);

        if (sessionInfo != null) {
            if (listSideTags != null) {
                sessionInfo.removeTagListReceivedListener(this);
            }
            sessionInfo.removeTorrentListReceivedListener(this);
            sessionInfo.removeTorrentListRefreshingListener(this);
            sessionInfo.removeSessionSettingsChangedListeners(this);
        }
        super.onPause();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        if (DEBUG) {
            Log.d(TAG, "onActivityCreated");
        }
        FragmentActivity activity = getActivity();
        tvFilteringBy = (TextView) activity.findViewById(R.id.wvFilteringBy);
        tvTorrentCount = (TextView) activity.findViewById(R.id.wvTorrentCount);
        tb = (Toolbar) activity.findViewById(R.id.toolbar_bottom);

        super.onActivityCreated(savedInstanceState);
    }

    public void finishActionMode() {
        if (mActionMode != null) {
            mActionMode.finish();
            mActionMode = null;
        }
        torrentListAdapter.clearChecked();
    }

    private static Map<?, ?>[] getCheckedTorrentMaps(TorrentListAdapter adapter) {
        if (adapter == null) {
            return new Map[0];
        }
        int[] checkedItems = adapter.getCheckedItemPositions();
        if (checkedItems.length == 0) {
            int selectedPosition = adapter.getSelectedPosition();
            if (selectedPosition < 0) {
                return new Map[0];
            }
            checkedItems = new int[] { selectedPosition };
        }

        List<Map> list = new ArrayList<>();

        for (int position : checkedItems) {
            Map<?, ?> torrent = adapter.getTorrentItem(position);
            if (torrent != null) {
                list.add(torrent);
            }
        }

        return list.toArray(new Map[list.size()]);
    }

    private static long[] getCheckedIDs(TorrentListAdapter adapter, boolean includeSelected) {
        if (adapter == null) {
            return new long[] {};
        }
        int[] checkedItems = adapter.getCheckedItemPositions();

        List<Long> list = new ArrayList<>();
        if (checkedItems.length == 0) {
            if (!includeSelected) {
                return new long[] {};
            }
            int selectedPosition = adapter.getSelectedPosition();
            if (selectedPosition < 0) {
                return new long[0];
            }
            long torrentID = adapter.getTorrentID(selectedPosition);
            return torrentID < 0 ? new long[0] : new long[] { torrentID };
        } else {
            for (int position : checkedItems) {
                long torrentID = adapter.getTorrentID(position);
                if (torrentID >= 0) {
                    list.add(torrentID);
                }
            }
        }

        long[] longs = new long[list.size()];
        for (int i = 0; i < list.size(); i++) {
            longs[i] = list.get(i);
        }

        return longs;
    }

    @Override
    public void rpcTorrentListReceived(String callID, List<?> addedTorrentMaps, List<?> removedTorrentIDs) {
        if ((addedTorrentMaps == null || addedTorrentMaps.size() == 0)
                && (removedTorrentIDs == null || removedTorrentIDs.size() == 0)) {
            return;
        }
        AndroidUtils.runOnUIThread(this, new AndroidUtils.RunnableWithActivity() {
            @Override
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                if (torrentListAdapter == null) {
                    return;
                }
                torrentListAdapter.refreshDisplayList();
            }
        });
    }

    /* (non-Javadoc)
        * @see android.support.v4.app.Fragment#onOptionsItemSelected(android.view
        * .MenuItem)
        */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "onOptionsItemSelected " + item.getTitle());
        }
        return handleFragmentMenuItems(item.getItemId()) || super.onOptionsItemSelected(item);
    }

    private boolean handleFragmentMenuItems(int itemId) {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "HANDLE MENU FRAG " + itemId);
        }
        if (sessionInfo == null) {
            return false;
        }

        if (itemId == R.id.action_filterby) {
            DialogFragmentFilterBy.openFilterByDialog(this, sessionInfo, sessionInfo.getRemoteProfile().getID());
            return true;
        } else if (itemId == R.id.action_filter) {
            boolean newVisibility = filterEditText.getVisibility() != View.VISIBLE;
            filterEditText.setVisibility(newVisibility ? View.VISIBLE : View.GONE);
            if (newVisibility) {
                filterEditText.requestFocus();
                InputMethodManager mgr = (InputMethodManager) getActivity()
                        .getSystemService(Context.INPUT_METHOD_SERVICE);
                mgr.showSoftInput(filterEditText, InputMethodManager.SHOW_IMPLICIT);
                VuzeEasyTracker.getInstance(this).sendEvent("uiAction", "ViewShown", "FilterBox", null);
            } else {
                InputMethodManager mgr = (InputMethodManager) getActivity()
                        .getSystemService(Context.INPUT_METHOD_SERVICE);
                mgr.hideSoftInputFromWindow(filterEditText.getWindowToken(), 0);
            }
            return true;
        } else if (itemId == R.id.action_sortby) {
            DialogFragmentSortBy.open(getFragmentManager(), this);
            return true;
        }
        return handleTorrentMenuActions(sessionInfo, getCheckedIDs(torrentListAdapter, true), getFragmentManager(),
                itemId);
    }

    public static boolean handleTorrentMenuActions(SessionInfo sessionInfo, final long[] ids, FragmentManager fm,
            int itemId) {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "HANDLE TORRENTMENU FRAG " + itemId);
        }
        if (sessionInfo == null || ids == null || ids.length == 0) {
            return false;
        }
        if (itemId == R.id.action_sel_remove) {
            for (long torrentID : ids) {
                Map<?, ?> map = sessionInfo.getTorrent(torrentID);
                long id = MapUtils.getMapLong(map, "id", -1);
                String name = MapUtils.getMapString(map, "name", "");
                // TODO: One at a time!
                DialogFragmentDeleteTorrent.open(fm, sessionInfo, name, id);
            }
            return true;
        } else if (itemId == R.id.action_sel_start) {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.startTorrents(TAG, ids, false, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_forcestart) {
            sessionInfo.executeRpc(new RpcExecuter() {

                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.startTorrents(TAG, ids, true, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_stop) {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.stopTorrents(TAG, ids, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_relocate)

        {
            Map<?, ?> mapFirst = sessionInfo.getTorrent(ids[0]);
            DialogFragmentMoveData.openMoveDataDialog(mapFirst, sessionInfo, fm);
            return true;
        } else if (itemId == R.id.action_sel_move_top)

        {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.simpleRpcCall("queue-move-top", ids, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_move_up)

        {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.simpleRpcCall("queue-move-up", ids, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_move_down)

        {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.simpleRpcCall("queue-move-down", ids, null);
                }
            });
            return true;
        } else if (itemId == R.id.action_sel_move_bottom)

        {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    rpc.simpleRpcCall("queue-move-bottom", ids, null);
                }
            });
            return true;
        }
        return false;

    }

    public void updateActionModeText(ActionMode mode) {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "MULTI:CHECK CHANGE");
        }

        if (mode != null) {
            String subtitle = getResources().getString(R.string.context_torrent_subtitle_selected,
                    torrentListAdapter.getCheckedItemCount());
            mode.setSubtitle(subtitle);
        }
    }

    private void setupActionModeCallback() {
        mActionModeCallbackV7 = new Callback() {

            // Called when the action mode is created; startActionMode() was called
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {

                if (AndroidUtils.DEBUG_MENU) {
                    Log.d(TAG, "onCreateActionMode");
                }

                if (mode == null && torrentListAdapter.getCheckedItemCount() == 0
                        && torrentListAdapter.getSelectedPosition() < 0) {
                    return false;
                }

                Menu origMenu = menu;
                if (tb != null) {
                    menu = tb.getMenu();
                }
                if (mode != null) {
                    mActionMode = (mode instanceof ActionModeWrapperV7) ? mode
                            : new ActionModeWrapperV7(mode, tb, getActivity());

                    mActionMode.setTitle(R.string.context_torrent_title);
                }
                ActionBarToolbarSplitter.buildActionBar(getActivity(), this, R.menu.menu_context_torrent_details,
                        menu, tb);

                TorrentDetailsFragment frag = (TorrentDetailsFragment) getActivity().getSupportFragmentManager()
                        .findFragmentById(R.id.frag_torrent_details);
                if (frag != null) {
                    frag.onCreateActionMode(mode, menu);
                }

                if (sideListArea == null) {
                    SubMenu subMenu = origMenu.addSubMenu(R.string.menu_global_actions);
                    subMenu.setIcon(R.drawable.ic_menu_white_24dp);
                    MenuItemCompat.setShowAsAction(subMenu.getItem(), MenuItemCompat.SHOW_AS_ACTION_NEVER);

                    try {
                        // Place "Global" actions on top bar in collapsed menu
                        MenuInflater mi = mode == null ? getActivity().getMenuInflater() : mode.getMenuInflater();
                        mi.inflate(R.menu.menu_torrent_list, subMenu);
                        onPrepareOptionsMenu(subMenu);
                    } catch (UnsupportedOperationException e) {
                        Log.e(TAG, e.getMessage());
                        menu.removeItem(subMenu.getItem().getItemId());
                    }
                }

                if (AndroidUtils.usesNavigationControl()) {
                    MenuItem add = origMenu.add(R.string.select_multiple_items);
                    add.setCheckable(true);
                    add.setChecked(torrentListAdapter.isMultiCheckMode());
                    add.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem item) {
                            boolean turnOn = !torrentListAdapter.isMultiCheckModeAllowed();

                            torrentListAdapter.setMultiCheckModeAllowed(turnOn);
                            if (turnOn) {
                                torrentListAdapter.setMultiCheckMode(turnOn);
                                torrentListAdapter.setItemChecked(torrentListAdapter.getSelectedPosition(), true);
                            }
                            return true;
                        }
                    });
                }

                return true;
            }

            // Called each time the action mode is shown. Always called after
            // onCreateActionMode, but
            // may be called multiple times if the mode is invalidated.
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {

                if (AndroidUtils.DEBUG_MENU) {
                    Log.d(TAG, "MULTI:onPrepareActionMode " + mode);
                }
                if (tb != null) {
                    menu = tb.getMenu();
                }

                // Must be called first, because our drawer sets all menu items
                // visible.. :(
                getActivity().onPrepareOptionsMenu(menu);

                prepareContextMenu(menu);

                TorrentDetailsFragment frag = (TorrentDetailsFragment) getActivity().getSupportFragmentManager()
                        .findFragmentById(R.id.frag_torrent_details);
                if (frag != null) {
                    frag.onPrepareActionMode(mode, menu);
                }

                AndroidUtils.fixupMenuAlpha(menu);

                return true;
            }

            // Called when the user selects a contextual menu item
            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                if (AndroidUtils.DEBUG_MENU) {
                    Log.d(TAG, "onActionItemClicked " + item.getTitle());
                }

                if (TorrentListFragment.this.handleFragmentMenuItems(item.getItemId())) {
                    return true;
                }
                if (getActivity().onOptionsItemSelected(item)) {
                    return true;
                }
                TorrentDetailsFragment frag = (TorrentDetailsFragment) getActivity().getSupportFragmentManager()
                        .findFragmentById(R.id.frag_torrent_details);
                if (frag != null) {
                    if (frag.onActionItemClicked(mode, item)) {
                        return true;
                    }
                }
                return false;
            }

            // Called when the user exits the action mode
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                if (AndroidUtils.DEBUG_MENU) {
                    Log.d(TAG, "onDestroyActionMode. BeingReplaced?" + actionModeBeingReplaced);
                }

                mActionMode = null;

                if (!actionModeBeingReplaced) {
                    listview.post(new Runnable() {
                        @Override
                        public void run() {
                            torrentListAdapter.setMultiCheckMode(false);
                            torrentListAdapter.clearChecked();
                            updateCheckedIDs();
                        }
                    });

                    listview.post(new Runnable() {
                        @Override
                        public void run() {
                            if (mCallback != null) {
                                mCallback.actionModeBeingReplacedDone();
                            }
                        }
                    });

                    listview.setLongClickable(true);
                    listview.requestLayout();
                    AndroidUtils.invalidateOptionsMenuHC(getActivity(), mActionMode);
                }
            }
        };
    }

    protected void prepareContextMenu(Menu menu) {
        boolean isLocalHost = sessionInfo != null && sessionInfo.getRemoteProfile().isLocalHost();
        boolean isOnlineOrLocal = VuzeRemoteApp.getNetworkState().isOnline() || isLocalHost;

        MenuItem menuMove = menu.findItem(R.id.action_sel_move);
        if (menuMove != null) {
            boolean enabled = isOnlineOrLocal && (torrentListAdapter.getCheckedItemCount() > 0
                    || torrentListAdapter.getSelectedPosition() >= 0);
            menuMove.setEnabled(enabled);
        }

        Map<?, ?>[] checkedTorrentMaps = getCheckedTorrentMaps(torrentListAdapter);
        boolean canStart = false;
        boolean canStop = false;
        if (isOnlineOrLocal) {
            for (Map<?, ?> mapTorrent : checkedTorrentMaps) {
                int status = MapUtils.getMapInt(mapTorrent, TransmissionVars.FIELD_TORRENT_STATUS,
                        TransmissionVars.TR_STATUS_STOPPED);
                canStart |= status == TransmissionVars.TR_STATUS_STOPPED;
                canStop |= status != TransmissionVars.TR_STATUS_STOPPED;
                if (AndroidUtils.DEBUG_MENU) {
                    Log.d(TAG, "prepareContextMenu: " + canStart + "/" + canStop + "/" + status);
                }
            }
        }
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "prepareContextMenu: " + canStart + "/" + canStop);
        }

        MenuItem menuStart = menu.findItem(R.id.action_sel_start);
        if (menuStart != null) {
            menuStart.setVisible(canStart);
            menuStart.setEnabled(canStart);
        } else {
            Log.d(TAG, "prepareContextMenu: No Start Menu!");
        }

        MenuItem menuStop = menu.findItem(R.id.action_sel_stop);
        if (menuStop != null) {
            menuStop.setVisible(canStop);
            menuStop.setEnabled(canStop);
        }

        AndroidUtilsUI.setManyMenuItemsEnabled(isOnlineOrLocal, menu, new int[] { R.id.action_sel_remove,
                R.id.action_sel_forcestart, R.id.action_sel_move, R.id.action_sel_relocate });
    }

    private boolean showContextualActions(boolean forceRebuild) {
        if (AndroidUtils.isTV()) {
            // TV doesn't get action bar changes, because it's impossible to get to
            // with remote control when you are on row 4000
            return false;
        }
        if (mActionMode != null && !forceRebuild) {
            if (AndroidUtils.DEBUG_MENU) {
                Log.d(TAG, "showContextualActions: invalidate existing");
            }
            mActionMode.invalidate();
            return false;
        }

        if (mCallback != null) {
            mCallback.setActionModeBeingReplaced(mActionMode, true);
        }
        // Start the CAB using the ActionMode.Callback defined above
        FragmentActivity activity = getActivity();
        if (activity instanceof AppCompatActivity) {
            AppCompatActivity abActivity = (AppCompatActivity) activity;
            if (AndroidUtils.DEBUG_MENU) {
                ActionBar ab = abActivity.getSupportActionBar();
                Log.d(TAG, "showContextualActions: startAB. mActionMode = " + mActionMode + "; isShowing="
                        + (ab == null ? null : ab.isShowing()));
            }

            actionModeBeingReplaced = true;

            ActionMode am = abActivity.startSupportActionMode(mActionModeCallbackV7);
            actionModeBeingReplaced = false;
            mActionMode = new ActionModeWrapperV7(am, tb, getActivity());
            mActionMode.setSubtitle(R.string.multi_select_tip);
            mActionMode.setTitle(R.string.context_torrent_title);
        }
        if (mCallback != null) {
            mCallback.setActionModeBeingReplaced(mActionMode, false);
        }

        return true;
    }

    @Override
    public void filterBy(final long filterMode, final String name, boolean save) {
        if (DEBUG) {
            Log.d(TAG, "FILTER BY " + name);
        }

        AndroidUtils.runOnUIThread(this, new Runnable() {
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                if (torrentListAdapter == null) {
                    if (DEBUG) {
                        Log.d(TAG, "No torrentListAdapter in filterBy");
                    }
                    return;
                }
                // java.lang.RuntimeException: Can't create handler inside thread that
                // has not called Looper.prepare()
                TorrentFilter filter = torrentListAdapter.getFilter();
                filter.setFilterMode(filterMode);
                if (tvFilteringBy != null) {
                    Map<?, ?> tag = sessionInfo.getTag(filterMode);
                    SpanTags spanTags = new SpanTags();
                    spanTags.init(getContext(), sessionInfo, tvFilteringBy, null);
                    spanTags.setCountFontRatio(0.8f);
                    if (tag == null) {
                        spanTags.addTagNames(Collections.singletonList(name));
                    } else {
                        ArrayList<Map<?, ?>> arrayList = new ArrayList<>(1);
                        arrayList.add(tag);
                        spanTags.setTagMaps(arrayList);
                    }
                    spanTags.setShowIcon(false);
                    spanTags.updateTags();
                } else {
                    if (DEBUG) {
                        Log.d(TAG, "null field in filterBy");
                    }
                }
            }
        });
        if (save) {
            sessionInfo.getRemoteProfile().setFilterBy(filterMode);
            sessionInfo.saveProfile();
        }
    }

    /* (non-Javadoc)
     * @see com.vuze.android.remote.dialog.DialogFragmentSortBy
     * .SortByDialogListener#sortBy(java.lang.String[], java.lang.Boolean[],
     * boolean)
     */
    @Override
    public void sortBy(final String[] sortFieldIDs, final Boolean[] sortOrderAsc, final int which, boolean save) {
        if (DEBUG) {
            Log.d(TAG, "SORT BY " + Arrays.toString(sortFieldIDs));
        }
        if (torrentListAdapter != null) {
            torrentListAdapter.setSort(sortFieldIDs, sortOrderAsc);
        }
        AndroidUtils.runOnUIThread(this, new Runnable() {
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                if (sideSortAdapter != null) {
                    sideSortAdapter.setCurrentSort(which, sortOrderAsc[0]);
                }

            }
        });

        if (save && sessionInfo != null) {
            sessionInfo.getRemoteProfile().setSortBy(sortFieldIDs, sortOrderAsc);
            sessionInfo.saveProfile();
        }
    }

    /* (non-Javadoc)
     * @see com.vuze.android.remote.dialog.DialogFragmentSortBy
     * .SortByDialogListener#flipSortOrder()
     */
    @Override
    public void flipSortOrder() {
        if (sessionInfo == null) {
            return;
        }
        RemoteProfile remoteProfile = sessionInfo.getRemoteProfile();
        if (remoteProfile == null) {
            return;
        }
        Boolean[] sortOrder = remoteProfile.getSortOrder();
        if (sortOrder == null) {
            return;
        }
        for (int i = 0; i < sortOrder.length; i++) {
            sortOrder[i] = !sortOrder[i];
        }
        sortBy(remoteProfile.getSortBy(), sortOrder,
                TorrentUtils.findSordIdFromTorrentFields(getContext(), remoteProfile.getSortBy()), true);
    }

    private void updateTorrentCount(final long total) {
        if (tvTorrentCount == null) {
            return;
        }
        AndroidUtils.runOnUIThread(this, new Runnable() {
            public void run() {
                if (getActivity() == null) {
                    return;
                }
                String s = "";
                if (total != 0) {
                    String constraint = torrentListAdapter.getFilter().getConstraint();
                    if (constraint != null && constraint.length() > 0) {
                        s = getResources().getQuantityString(R.plurals.torrent_count, (int) total, total);
                    }
                }
                tvTorrentCount.setText(s);
            }
        });
    }

    /* (non-Javadoc)
     * @see com.vuze.android.remote.fragment
     * .ActionModeBeingReplacedListener#setActionModeBeingReplaced(boolean)
     */
    @Override
    public void setActionModeBeingReplaced(ActionMode actionMode, boolean actionModeBeingReplaced) {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "setActionModeBeingReplaced: replaced? " + actionModeBeingReplaced + "; hasActionMode? "
                    + (mActionMode != null));
        }
        this.actionModeBeingReplaced = actionModeBeingReplaced;
        if (actionModeBeingReplaced) {
            rebuildActionMode = mActionMode != null;
            if (rebuildActionMode) {
                mActionMode.finish();
                mActionMode = null;
            }
        }
    }

    /* (non-Javadoc)
     * @see com.vuze.android.remote.fragment
     * .ActionModeBeingReplacedListener#actionModeBeingReplacedDone()
     */
    @Override
    public void actionModeBeingReplacedDone() {
        if (AndroidUtils.DEBUG_MENU) {
            Log.d(TAG, "actionModeBeingReplacedDone: rebuild? " + rebuildActionMode);
        }
        if (rebuildActionMode) {
            rebuildActionMode = false;

            rebuildActionMode();
            torrentListAdapter.setMultiCheckMode(false);
        }
    }

    /* (non-Javadoc)
     * @see com.vuze.android.remote.fragment
     * .ActionModeBeingReplacedListener#getActionMode()
     */
    @Override
    public ActionMode getActionMode() {
        return mActionMode;
    }

    public void clearSelection() {
        finishActionMode();
    }

    private void updateCheckedIDs() {
        long[] checkedTorrentIDs = getCheckedIDs(torrentListAdapter, false);
        if (mCallback != null) {
            mCallback.onTorrentSelectedListener(TorrentListFragment.this, checkedTorrentIDs,
                    torrentListAdapter.isMultiCheckMode());
        }
        if (checkedTorrentIDs.length == 0 && mActionMode != null) {
            mActionMode.finish();
        }
    }

    @Override
    public void rebuildActionMode() {
        showContextualActions(true);
    }

    public void startStopTorrents() {
        Map<?, ?>[] checkedTorrentMaps = getCheckedTorrentMaps(torrentListAdapter);
        if (checkedTorrentMaps == null || checkedTorrentMaps.length == 0) {
            return;
        }
        //boolean canStart = false;
        boolean canStop = false;
        for (Map<?, ?> mapTorrent : checkedTorrentMaps) {
            int status = MapUtils.getMapInt(mapTorrent, TransmissionVars.FIELD_TORRENT_STATUS,
                    TransmissionVars.TR_STATUS_STOPPED);
            //canStart |= status == TransmissionVars.TR_STATUS_STOPPED;
            canStop |= status != TransmissionVars.TR_STATUS_STOPPED;
        }

        if (!canStop) {
            sessionInfo.executeRpc(new RpcExecuter() {
                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    long[] ids = getCheckedIDs(torrentListAdapter, true);
                    rpc.stopTorrents(TAG, ids, null);
                }
            });
        } else {
            sessionInfo.executeRpc(new RpcExecuter() {

                @Override
                public void executeRpc(TransmissionRPC rpc) {
                    long[] ids = getCheckedIDs(torrentListAdapter, true);
                    rpc.startTorrents(TAG, ids, false, null);
                }

            });
        }
    }

    @Override
    public Callback getActionModeCallback() {
        return mActionModeCallbackV7;
    }

    private boolean isViewContains(View view, int rx, int ry) {
        Rect rect = new Rect();
        view.getGlobalVisibleRect(rect);

        return rect.contains(rx, ry);
    }

    public void onTouchEvent(MotionEvent event) {
        if (sideListArea == null) {
            return;
        }
        int action = event.getAction() & 0xff;
        if (action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_POINTER_DOWN) {
            return;
        }
        boolean newHasFocus = isViewContains(sideListArea, (int) event.getX(), (int) event.getY());
        if ((sidelistIsExpanded == null || sidelistIsExpanded) && !newHasFocus) {
            //left focus
            sidelistInFocus = false;
            expandSideListWidth(false);
            // uncomment this if you want the sidelist to expand upon touch of
            // sidelist area
            //      } else if ((sidelistIsExpanded == null || !sidelistIsExpanded)
            //            && newHasFocus) {
            //         expandSideListWidth(true);
            //         sidelistInFocus = true;
        }
    }

    @Override
    public void tagListReceived(List<Map<?, ?>> tags) {
        if (sideTagAdapter == null || tags == null) {
            return;
        }
        List<SideTagAdapter.SideTagInfo> list = new ArrayList<>();
        for (Map tag : tags) {
            if (MapUtils.getMapLong(tag, "count", 0) > 0) {
                list.add(new SideTagAdapter.SideTagInfo(tag));
            }
        }
        sideTagAdapter.setItems(list);
    }

}