Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.dvr.ui;
     18 
     19 import android.support.annotation.VisibleForTesting;
     20 import android.support.v17.leanback.widget.ArrayObjectAdapter;
     21 import android.support.v17.leanback.widget.PresenterSelector;
     22 import com.android.tv.common.SoftPreconditions;
     23 import java.util.ArrayList;
     24 import java.util.Collection;
     25 import java.util.Collections;
     26 import java.util.Comparator;
     27 import java.util.HashSet;
     28 import java.util.List;
     29 import java.util.Set;
     30 
     31 /**
     32  * Keeps a set of items sorted
     33  *
     34  * <p>{@code T} must have stable IDs.
     35  */
     36 public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
     37     private final Comparator<T> mComparator;
     38     private final int mMaxItemCount;
     39     private int mExtraItemCount;
     40     private final Set<Long> mIds = new HashSet<>();
     41 
     42     public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
     43         this(presenterSelector, comparator, Integer.MAX_VALUE);
     44     }
     45 
     46     public SortedArrayAdapter(
     47             PresenterSelector presenterSelector, Comparator<T> comparator, int maxItemCount) {
     48         super(presenterSelector);
     49         mComparator = comparator;
     50         mMaxItemCount = maxItemCount;
     51         setHasStableIds(true);
     52     }
     53 
     54     /**
     55      * Sets the objects in the given collection to the adapter keeping the elements sorted.
     56      *
     57      * @param items A {@link Collection} of items to be set.
     58      */
     59     @VisibleForTesting
     60     final void setInitialItems(List<T> items) {
     61         List<T> itemsCopy = new ArrayList<>(items);
     62         Collections.sort(itemsCopy, mComparator);
     63         for (T item : itemsCopy) {
     64             add(item, true);
     65             if (size() == mMaxItemCount) {
     66                 break;
     67             }
     68         }
     69     }
     70 
     71     /**
     72      * Adds an item in sorted order to the adapter.
     73      *
     74      * @param item The item to add in sorted order to the adapter.
     75      */
     76     @Override
     77     public final void add(Object item) {
     78         add((T) item, false);
     79     }
     80 
     81     public boolean isEmpty() {
     82         return size() == 0;
     83     }
     84 
     85     /**
     86      * Adds an item in sorted order to the adapter.
     87      *
     88      * @param item The item to add in sorted order to the adapter.
     89      * @param insertToEnd If items are inserted in a more or less sorted fashion, sets this
     90      *     parameter to {@code true} to search insertion position from the end to save search time.
     91      */
     92     public final void add(T item, boolean insertToEnd) {
     93         long newItemId = getId(item);
     94         SoftPreconditions.checkState(!mIds.contains(newItemId));
     95         mIds.add(newItemId);
     96         int i;
     97         if (insertToEnd) {
     98             i = findInsertPosition(item);
     99         } else {
    100             i = findInsertPositionBinary(item);
    101         }
    102         super.add(i, item);
    103         if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) {
    104             Object removedItem = get(mMaxItemCount);
    105             remove(removedItem);
    106         }
    107     }
    108 
    109     /**
    110      * Adds an extra item to the end of the adapter. The items will not be subjected to the sorted
    111      * order or the maximum number of items. One or more extra items can be added to the adapter.
    112      * They will be presented in their insertion order.
    113      */
    114     public int addExtraItem(T item) {
    115         long newItemId = getId(item);
    116         SoftPreconditions.checkState(!mIds.contains(newItemId));
    117         mIds.add(newItemId);
    118         super.add(item);
    119         return ++mExtraItemCount;
    120     }
    121 
    122     @Override
    123     public boolean remove(Object item) {
    124         return removeWithId((T) item);
    125     }
    126 
    127     /** Removes an item which has the same ID as {@code item}. */
    128     public boolean removeWithId(T item) {
    129         int index = indexWithId(item);
    130         return index >= 0 && index < size() && removeItems(index, 1) == 1;
    131     }
    132 
    133     @Override
    134     public int removeItems(int position, int count) {
    135         int upperBound = Math.min(position + count, size());
    136         for (int i = position; i < upperBound; i++) {
    137             mIds.remove(getId((T) get(i)));
    138         }
    139         if (upperBound > size() - mExtraItemCount) {
    140             mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position);
    141         }
    142         return super.removeItems(position, count);
    143     }
    144 
    145     @Override
    146     public void replace(int position, Object item) {
    147         boolean wasExtra = position >= size() - mExtraItemCount;
    148         removeItems(position, 1);
    149         if (!wasExtra) {
    150             add(item);
    151         } else {
    152             addExtraItem((T) item);
    153         }
    154     }
    155 
    156     @Override
    157     public void clear() {
    158         mIds.clear();
    159         super.clear();
    160     }
    161 
    162     /**
    163      * Changes an item in the list.
    164      *
    165      * @param item The item to change.
    166      */
    167     public final void change(T item) {
    168         int oldIndex = indexWithId(item);
    169         if (oldIndex != -1) {
    170             T old = (T) get(oldIndex);
    171             if (mComparator.compare(old, item) == 0) {
    172                 replace(oldIndex, item);
    173                 return;
    174             }
    175             remove(old);
    176         }
    177         add(item);
    178     }
    179 
    180     /** Checks whether the item is in the list. */
    181     public final boolean contains(T item) {
    182         return indexWithId(item) != -1;
    183     }
    184 
    185     @Override
    186     public long getId(int position) {
    187         return getId((T) get(position));
    188     }
    189 
    190     /**
    191      * Returns the id of the the given {@code item}, which will be used in {@link #change} to decide
    192      * if the given item is already existed in the adapter.
    193      *
    194      * <p>The id must be stable.
    195      */
    196     protected abstract long getId(T item);
    197 
    198     private int indexWithId(T item) {
    199         long id = getId(item);
    200         for (int i = 0; i < size() - mExtraItemCount; i++) {
    201             T r = (T) get(i);
    202             if (getId(r) == id) {
    203                 return i;
    204             }
    205         }
    206         return -1;
    207     }
    208 
    209     /** Finds the position that the given item should be inserted to keep the sorted order. */
    210     public int findInsertPosition(T item) {
    211         for (int i = size() - mExtraItemCount - 1; i >= 0; i--) {
    212             T r = (T) get(i);
    213             if (mComparator.compare(r, item) <= 0) {
    214                 return i + 1;
    215             }
    216         }
    217         return 0;
    218     }
    219 
    220     private int findInsertPositionBinary(T item) {
    221         int lb = 0;
    222         int ub = size() - mExtraItemCount - 1;
    223         while (lb <= ub) {
    224             int mid = (lb + ub) / 2;
    225             T r = (T) get(mid);
    226             int compareResult = mComparator.compare(item, r);
    227             if (compareResult == 0) {
    228                 return mid;
    229             } else if (compareResult > 0) {
    230                 lb = mid + 1;
    231             } else {
    232                 ub = mid - 1;
    233             }
    234         }
    235         return lb;
    236     }
    237 }
    238