com.vuze.android.remote.adapter.TorrentListAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.android.remote.adapter.TorrentListAdapter.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.adapter;

import java.util.*;

import android.content.Context;
import android.support.v4.util.LongSparseArray;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;

import com.vuze.android.FlexibleRecyclerAdapter;
import com.vuze.android.FlexibleRecyclerSelectionListener;
import com.vuze.android.remote.*;
import com.vuze.android.remote.TextViewFlipper.FlipValidator;
import com.vuze.android.remote.fragment.TorrentListFragment;
import com.vuze.util.MapUtils;

/**
 * Checked == Activated according to google.  In google docs for View
 * .setActivated:
 * (Um, yeah, we are deeply sorry about the terminology here.)
 * <p/>
 * </p>
 * Other terms:
 * Focused: One focus per screen
 * Selected: highlighted item(s).  May not be activated
 * Checked: activated item(s)
 */
public class TorrentListAdapter extends FlexibleRecyclerAdapter<TorrentListViewHolder, Long> implements Filterable {
    public final static int FILTERBY_ALL = 8;

    public final static int FILTERBY_ACTIVE = 4;

    public final static int FILTERBY_COMPLETE = 9;

    public final static int FILTERBY_INCOMPLETE = 1;

    public final static int FILTERBY_STOPPED = 2;

    public static final boolean DEBUG = AndroidUtils.DEBUG_ADAPTER;

    private static final String TAG = "TorrentListAdapter";

    private ComparatorMapFields sorter;

    private int viewType;

    public static class ViewHolderFlipValidator implements FlipValidator {
        private TorrentListViewHolder holder;

        private long torrentID;

        public ViewHolderFlipValidator(TorrentListViewHolder holder, long torrentID) {
            this.holder = holder;
            this.torrentID = torrentID;
        }

        @Override
        public boolean isStillValid() {
            return holder.torrentID == torrentID;
        }
    }

    private Context context;

    private TorrentFilter filter;

    public final Object mLock = new Object();

    private SessionInfo sessionInfo;

    private TorrentListRowFiller torrentListRowFiller;

    private boolean isRefreshing;

    public TorrentListAdapter(Context context, FlexibleRecyclerSelectionListener selector) {
        super(selector);
        this.context = context;

        torrentListRowFiller = new TorrentListRowFiller(context);

        sorter = new ComparatorMapFields() {

            public Throwable lastError;

            @Override
            public int reportError(Comparable<?> oLHS, Comparable<?> oRHS, Throwable t) {
                if (lastError != null) {
                    if (t.getCause().equals(lastError.getCause())
                            && t.getMessage().equals(lastError.getMessage())) {
                        return 0;
                    }
                }
                lastError = t;
                Log.e(TAG, "TorrentSort", t);
                VuzeEasyTracker.getInstance(TorrentListAdapter.this.context).logError(t);
                return 0;
            }

            @Override
            public Map<?, ?> mapGetter(Object o) {
                return sessionInfo.getTorrent((Long) o);
            }

            @SuppressWarnings("rawtypes")
            @Override
            public Comparable modifyField(String fieldID, Map map, Comparable o) {
                if (fieldID.equals(TransmissionVars.FIELD_TORRENT_POSITION)) {
                    return (MapUtils.getMapLong(map, TransmissionVars.FIELD_TORRENT_LEFT_UNTIL_DONE, 1) == 0
                            ? 0x1000000000000000L
                            : 0) + ((Number) o).longValue();
                }
                if (fieldID.equals(TransmissionVars.FIELD_TORRENT_ETA)) {
                    if (((Number) o).longValue() < 0) {
                        o = Long.MAX_VALUE;
                    }
                }
                return o;
            }
        };
    }

    public void lettersUpdated(HashMap<String, Integer> setLetters) {

    }

    public void setSessionInfo(SessionInfo sessionInfo) {
        this.sessionInfo = sessionInfo;
    }

    @Override
    public TorrentFilter getFilter() {
        if (filter == null) {
            filter = new TorrentFilter();
        }
        return filter;
    }

    public class TorrentFilter extends Filter {
        private long filterMode;

        private String constraint;

        private boolean compactDigits = true;

        private boolean compactNonLetters = true;

        private boolean buildLetters = false;

        private boolean compactPunctuation = true;

        public void setFilterMode(long filterMode) {
            this.filterMode = filterMode;
            filter(constraint);
        }

        public void setBuildLetters(boolean buildLetters) {
            this.buildLetters = buildLetters;
        }

        public void refilter() {
            filter(constraint);
        }

        @Override
        protected FilterResults performFiltering(CharSequence _constraint) {
            synchronized (mLock) {
                isRefreshing = false;
            }

            this.constraint = _constraint == null ? null : _constraint.toString().toUpperCase(Locale.US);
            FilterResults results = new FilterResults();

            if (sessionInfo == null) {
                if (DEBUG) {
                    Log.d(TAG, "performFiltering skipped: No sessionInfo");
                }
                return results;
            }

            boolean hasConstraint = buildLetters || (constraint != null && constraint.length() > 0);

            LongSparseArray<Map<?, ?>> torrentList = sessionInfo.getTorrentListSparseArray();
            int size = torrentList.size();

            if (DEBUG) {
                Log.d(TAG, "performFiltering: size=" + size + "/hasConstraint? " + hasConstraint + "/filter="
                        + filterMode);
            }

            if (size > 0 && (hasConstraint || filterMode > 0)) {
                if (DEBUG) {
                    Log.d(TAG, "filtering " + torrentList.size());
                }

                if (filterMode >= 0 && filterMode != FILTERBY_ALL) {
                    synchronized (mLock) {
                        for (int i = size - 1; i >= 0; i--) {
                            long key = torrentList.keyAt(i);

                            if (!filterCheck(filterMode, key)) {
                                torrentList.removeAt(i);
                                size--;
                            }
                        }
                    }
                }

                if (DEBUG) {
                    Log.d(TAG, "type filtered to " + size);
                }

                if (hasConstraint) {
                    if (constraint == null) {
                        constraint = "";
                    }
                    HashSet<String> setLetters = null;
                    HashMap<String, Integer> mapLetterCount = null;
                    if (buildLetters) {
                        setLetters = new HashSet<>();
                        mapLetterCount = new HashMap<>();
                    }
                    synchronized (mLock) {
                        for (int i = size - 1; i >= 0; i--) {
                            long key = torrentList.keyAt(i);

                            if (!constraintCheck(constraint, key, setLetters, constraint, compactDigits,
                                    compactNonLetters, compactPunctuation)) {
                                torrentList.removeAt(i);
                                size--;
                            }

                            if (buildLetters && setLetters.size() > 0) {
                                for (String letter : setLetters) {
                                    Integer count = mapLetterCount.get(letter);
                                    if (count == null) {
                                        count = 1;
                                    } else {
                                        count++;
                                    }
                                    mapLetterCount.put(letter, count);
                                }
                                setLetters.clear();
                            }
                        }
                    }

                    if (buildLetters) {
                        lettersUpdated(mapLetterCount);
                    }

                    if (DEBUG) {
                        Log.d(TAG, "text filtered to " + size);
                    }
                }
            }
            int num = torrentList.size();
            ArrayList<Long> keys = new ArrayList<>(num);
            for (int i = 0; i < num; i++) {
                keys.add(torrentList.keyAt(i));
            }

            doSort(keys, sorter, false);

            results.values = keys;
            results.count = keys.size();

            return results;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            // Now we have to inform the adapter about the new list filtered
            if (results.count == 0) {
                removeAllItems();
            } else {
                synchronized (mLock) {
                    if (results.values instanceof List) {
                        setItems((List<Long>) results.values);
                    }
                }
            }
        }

        public boolean getCompactDigits() {
            return compactDigits;
        }

        public boolean setCompactDigits(boolean compactDigits) {
            if (this.compactDigits == compactDigits) {
                return false;
            }
            this.compactDigits = compactDigits;
            return true;
        }

        public boolean getCompactNonLetters() {
            return compactNonLetters;
        }

        public boolean setCompactOther(boolean compactNonLetters) {
            if (this.compactNonLetters == compactNonLetters) {
                return false;
            }
            this.compactNonLetters = compactNonLetters;
            return true;
        }

        public boolean setCompactPunctuation(boolean compactPunctuation) {
            if (this.compactPunctuation == compactPunctuation) {
                return false;
            }
            this.compactPunctuation = compactPunctuation;
            return true;
        }

        public boolean getCompactPunctuation() {
            return compactPunctuation;
        }

        public String getConstraint() {
            return constraint;
        }
    }

    public void refreshDisplayList() {
        synchronized (mLock) {
            if (isRefreshing) {
                if (AndroidUtils.DEBUG) {
                    Log.d(TAG, "skipped refreshDisplayList");
                }
                return;
            }
            isRefreshing = true;
        }
        getFilter().refilter();
    }

    public boolean constraintCheck(CharSequence constraint, long torrentID, HashSet<String> setLetters,
            String charAfter, boolean compactDigits, boolean compactNonLetters, boolean compactPunctuation) {
        if (setLetters == null && (constraint == null || constraint.length() == 0)) {
            return true;
        }
        Map<?, ?> map = sessionInfo.getTorrent(torrentID);
        if (map == null) {
            return false;
        }

        String name = MapUtils.getMapString(map, TransmissionVars.FIELD_TORRENT_NAME, "").toUpperCase(Locale.US);
        if (setLetters != null) {
            int nameLength = name.length();
            if (charAfter.length() > 0) {
                int pos = name.indexOf(charAfter);
                while (pos >= 0) {
                    int end = pos + charAfter.length();
                    if (end < nameLength) {
                        char c = name.charAt(end);
                        boolean isDigit = Character.isDigit(c);
                        if (compactDigits && isDigit) {
                            setLetters.add(TorrentListFragment.LETTERS_NUMBERS);
                        } else if (compactPunctuation && isStandardPuncuation(c)) {
                            setLetters.add(TorrentListFragment.LETTERS_PUNCTUATION);
                        } else if (compactNonLetters && !isDigit && !isAlphabetic(c) && !isStandardPuncuation(c)) {
                            setLetters.add(TorrentListFragment.LETTERS_NON);
                        } else {
                            setLetters.add(Character.toString(c));
                        }
                    }
                    pos = name.indexOf(charAfter, pos + 1);
                }
            } else {
                for (int i = 0; i < nameLength; i++) {
                    char c = name.charAt(i);
                    boolean isDigit = Character.isDigit(c);
                    if (compactDigits && isDigit) {
                        setLetters.add(TorrentListFragment.LETTERS_NUMBERS);
                    } else if (compactPunctuation && isStandardPuncuation(c)) {
                        setLetters.add(TorrentListFragment.LETTERS_PUNCTUATION);
                    } else if (compactNonLetters && !isDigit && !isAlphabetic(c) && !isStandardPuncuation(c)) {
                        setLetters.add(TorrentListFragment.LETTERS_NON);
                    } else {
                        setLetters.add(Character.toString(c));
                    }
                }
            }
        }
        if (constraint == null || constraint.length() == 0) {
            return true;
        }
        return name.contains(constraint);
    }

    private static boolean isAlphabetic(int c) {
        // Seems to return symbolic languages
        //      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        //         return Character.isAlphabetic(c);
        //      }
        if (!Character.isLetter(c)) {
            return false;
        }
        int type = Character.getType(c);
        return type == Character.UPPERCASE_LETTER || type == Character.LOWERCASE_LETTER;
        // Simple, but doesn't include letters with hats on them ;)
        //return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
    }

    private static boolean isStandardPuncuation(int c) {
        int type = Character.getType(c);
        return type == Character.START_PUNCTUATION || type == Character.END_PUNCTUATION
                || type == Character.OTHER_PUNCTUATION;
    }

    private boolean filterCheck(long filterMode, long torrentID) {
        Map<?, ?> map = sessionInfo.getTorrent(torrentID);
        if (map == null) {
            return false;
        }

        if (filterMode > 10) {
            List<?> listTagUIDs = MapUtils.getMapList(map, "tag-uids", null);
            return listTagUIDs != null && listTagUIDs.contains(filterMode);
        }

        switch ((int) filterMode) {
        case FILTERBY_ACTIVE:
            long dlRate = MapUtils.getMapLong(map, TransmissionVars.FIELD_TORRENT_RATE_DOWNLOAD, -1);
            long ulRate = MapUtils.getMapLong(map, TransmissionVars.FIELD_TORRENT_RATE_UPLOAD, -1);
            if (ulRate <= 0 && dlRate <= 0) {
                return false;
            }
            break;

        case FILTERBY_COMPLETE: {
            float pctDone = MapUtils.getMapFloat(map, TransmissionVars.FIELD_TORRENT_PERCENT_DONE, 0);
            if (pctDone < 1.0f) {
                return false;
            }
            break;
        }
        case FILTERBY_INCOMPLETE: {
            float pctDone = MapUtils.getMapFloat(map, TransmissionVars.FIELD_TORRENT_PERCENT_DONE, 0);
            if (pctDone >= 1.0f) {
                return false;
            }
            break;
        }
        case FILTERBY_STOPPED: {
            int status = MapUtils.getMapInt(map, TransmissionVars.FIELD_TORRENT_STATUS,
                    TransmissionVars.TR_STATUS_STOPPED);
            if (status != TransmissionVars.TR_STATUS_STOPPED) {
                return false;
            }
            break;
        }
        }
        return true;
    }

    public void setSort(String[] fieldIDs, Boolean[] sortOrderAsc) {
        synchronized (mLock) {
            Boolean[] order;
            if (sortOrderAsc == null) {
                order = new Boolean[fieldIDs.length];
                Arrays.fill(order, Boolean.FALSE);
            } else if (sortOrderAsc.length != fieldIDs.length) {
                order = new Boolean[fieldIDs.length];
                Arrays.fill(order, Boolean.FALSE);
                System.arraycopy(sortOrderAsc, 0, order, 0, sortOrderAsc.length);
            } else {
                order = sortOrderAsc;
            }
            sorter.setSortFields(fieldIDs, order);
        }
        doSort();
    }

    public void setSort(Comparator<? super Map<?, ?>> comparator) {
        synchronized (mLock) {
            sorter.setComparator(comparator);
        }
        doSort();
    }

    private void doSort() {
        if (sessionInfo == null) {
            if (DEBUG) {
                Log.d(TAG, "doSort skipped: No sessionInfo");
            }
            return;
        }
        if (!sorter.isValid()) {
            if (DEBUG) {
                Log.d(TAG, "doSort skipped: no comparator and no sort");
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "sort: " + sorter.toDebugString());
        }

        sortItems(sorter);
    }

    public Map<?, ?> getTorrentItem(int position) {
        if (sessionInfo == null) {
            return new HashMap<Object, Object>();
        }
        Long item = getItem(position);
        if (item == null) {
            return new HashMap<Object, Object>();
        }
        return sessionInfo.getTorrent(item);
    }

    public long getTorrentID(int position) {
        Long torrentID = getItem(position);
        return torrentID == null ? -1 : torrentID;
    }

    public void setViewType(int viewType) {
        this.viewType = viewType;
        notifyDataSetInvalidated();
    }

    @Override
    public int getItemViewType(int position) {
        return viewType;
    }

    @SuppressWarnings("unchecked")
    @Override
    public TorrentListViewHolder onCreateFlexibleViewHolder(ViewGroup parent, int viewType) {
        boolean isSmall = viewType == 1;
        int resourceID = isSmall ? R.layout.row_torrent_list_small : R.layout.row_torrent_list;
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View rowView = inflater.inflate(resourceID, parent, false);
        TorrentListViewHolder holder = new TorrentListViewHolder(this, rowView, isSmall);

        rowView.setTag(holder);
        return holder;
    }

    @Override
    public void onBindFlexibleViewHolder(TorrentListViewHolder holder, int position) {
        Map<?, ?> item = getTorrentItem(position);
        torrentListRowFiller.fillHolder(holder, item, sessionInfo);
        if (AndroidUtils.isTV()) {
            // Torrent List goes to bottom of TV screen, past the overscan area
            // Adjust last item to have overscan gap, to ensure user can view
            // the whole row

            // Setting bottomMargin on itemView doesn't work on FireTV
            // Try layoutRow instead.  The side affect is that we will be extending
            // the selector state color to the bottom of the screen, which doesn't
            // look great
            View v = holder.layoutRow == null ? holder.itemView : holder.layoutRow;
            ViewGroup.LayoutParams lp = v.getLayoutParams();
            int paddingBottom = position + 1 == getItemCount() ? AndroidUtilsUI.dpToPx(48) : 0;

            if (lp instanceof RecyclerView.LayoutParams) {
                ((RecyclerView.LayoutParams) lp).bottomMargin = paddingBottom;
            } else if (lp instanceof RelativeLayout.LayoutParams) {
                ((RelativeLayout.LayoutParams) lp).bottomMargin = paddingBottom;
            } else if (lp instanceof FrameLayout.LayoutParams) {
                // shouldn't happen, but this is the layout param type for the row
                ((FrameLayout.LayoutParams) lp).bottomMargin = paddingBottom;
            }
            v.requestLayout();
        }
    }

    @Override
    public long getItemId(int position) {
        return getTorrentID(position) << viewType;
    }
}