• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
23 import com.android.tv.common.SoftPreconditions;
24 
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32 
33 /**
34  * Keeps a set of items sorted
35  *
36  * <p>{@code T} must have stable IDs.
37  */
38 public abstract class SortedArrayAdapter<T> extends ArrayObjectAdapter {
39     private final Comparator<T> mComparator;
40     private final int mMaxItemCount;
41     private int mExtraItemCount;
42     private final Set<Long> mIds = new HashSet<>();
43 
SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator)44     public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator) {
45         this(presenterSelector, comparator, Integer.MAX_VALUE);
46     }
47 
SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator, int maxItemCount)48     public SortedArrayAdapter(PresenterSelector presenterSelector, Comparator<T> comparator,
49             int maxItemCount) {
50         super(presenterSelector);
51         mComparator = comparator;
52         mMaxItemCount = maxItemCount;
53         setHasStableIds(true);
54     }
55 
56     /**
57      * Sets the objects in the given collection to the adapter keeping the elements sorted.
58      *
59      * @param items A {@link Collection} of items to be set.
60      */
61     @VisibleForTesting
setInitialItems(List<T> items)62     final void setInitialItems(List<T> items) {
63         List<T> itemsCopy = new ArrayList<>(items);
64         Collections.sort(itemsCopy, mComparator);
65         for (T item : itemsCopy) {
66             add(item, true);
67             if (size() == mMaxItemCount) {
68                 break;
69             }
70         }
71     }
72 
73     /**
74      * Adds an item in sorted order to the adapter.
75      *
76      * @param item The item to add in sorted order to the adapter.
77      */
78     @Override
add(Object item)79     public final void add(Object item) {
80         add((T) item, false);
81     }
82 
isEmpty()83     public boolean isEmpty() {
84         return size() == 0;
85     }
86 
87     /**
88      * Adds an item in sorted order to the adapter.
89      *
90      * @param item The item to add in sorted order to the adapter.
91      * @param insertToEnd If items are inserted in a more or less sorted fashion,
92      *                    sets this parameter to {@code true} to search insertion position from
93      *                    the end to save search time.
94      */
add(T item, boolean insertToEnd)95     public final void add(T item, boolean insertToEnd) {
96         long newItemId = getId(item);
97         SoftPreconditions.checkState(!mIds.contains(newItemId));
98         mIds.add(newItemId);
99         int i;
100         if (insertToEnd) {
101             i = findInsertPosition(item);
102         } else {
103             i = findInsertPositionBinary(item);
104         }
105         super.add(i, item);
106         if (mMaxItemCount < Integer.MAX_VALUE && size() > mMaxItemCount + mExtraItemCount) {
107             Object removedItem = get(mMaxItemCount);
108             remove(removedItem);
109         }
110     }
111 
112     /**
113      * Adds an extra item to the end of the adapter. The items will not be subjected to the sorted
114      * order or the maximum number of items. One or more extra items can be added to the adapter.
115      * They will be presented in their insertion order.
116      */
addExtraItem(T item)117     public int addExtraItem(T item) {
118         long newItemId = getId(item);
119         SoftPreconditions.checkState(!mIds.contains(newItemId));
120         mIds.add(newItemId);
121         super.add(item);
122         return ++mExtraItemCount;
123     }
124 
125     @Override
remove(Object item)126     public boolean remove(Object item) {
127         return removeWithId((T) item);
128     }
129 
130     /**
131      * Removes an item which has the same ID as {@code item}.
132      */
removeWithId(T item)133     public boolean removeWithId(T item) {
134         int index = indexWithId(item);
135         return index >= 0 && index < size() && removeItems(index, 1) == 1;
136     }
137 
138     @Override
removeItems(int position, int count)139     public int removeItems(int position, int count) {
140         int upperBound = Math.min(position + count, size());
141         for (int i = position; i < upperBound; i++) {
142             mIds.remove(getId((T) get(i)));
143         }
144         if (upperBound > size() - mExtraItemCount) {
145             mExtraItemCount -= upperBound - Math.max(size() - mExtraItemCount, position);
146         }
147         return super.removeItems(position, count);
148     }
149 
150     @Override
replace(int position, Object item)151     public void replace(int position, Object item) {
152         boolean wasExtra = position >= size() - mExtraItemCount;
153         removeItems(position, 1);
154         if (!wasExtra) {
155             add(item);
156         } else {
157             addExtraItem((T) item);
158         }
159     }
160 
161     @Override
clear()162     public void clear() {
163         mIds.clear();
164         super.clear();
165     }
166 
167     /**
168      * Changes an item in the list.
169      * @param item The item to change.
170      */
change(T item)171     public final void change(T item) {
172         int oldIndex = indexWithId(item);
173         if (oldIndex != -1) {
174             T old = (T) get(oldIndex);
175             if (mComparator.compare(old, item) == 0) {
176                 replace(oldIndex, item);
177                 return;
178             }
179             remove(old);
180         }
181         add(item);
182     }
183 
184     /**
185      * Checks whether the item is in the list.
186      */
contains(T item)187     public final boolean contains(T item) {
188         return indexWithId(item) != -1;
189     }
190 
191     @Override
getId(int position)192     public long getId(int position) {
193         return getId((T) get(position));
194     }
195 
196     /**
197      * Returns the id of the the given {@code item}, which will be used in {@link #change} to
198      * decide if the given item is already existed in the adapter.
199      *
200      * The id must be stable.
201      */
getId(T item)202     protected abstract long getId(T item);
203 
indexWithId(T item)204     private int indexWithId(T item) {
205         long id = getId(item);
206         for (int i = 0; i < size() - mExtraItemCount; i++) {
207             T r = (T) get(i);
208             if (getId(r) == id) {
209                 return i;
210             }
211         }
212         return -1;
213     }
214 
215     /**
216      * Finds the position that the given item should be inserted to keep the sorted order.
217      */
findInsertPosition(T item)218     public int findInsertPosition(T item) {
219         for (int i = size() - mExtraItemCount - 1; i >=0; i--) {
220             T r = (T) get(i);
221             if (mComparator.compare(r, item) <= 0) {
222                 return i + 1;
223             }
224         }
225         return 0;
226     }
227 
findInsertPositionBinary(T item)228     private int findInsertPositionBinary(T item) {
229         int lb = 0;
230         int ub = size() - mExtraItemCount - 1;
231         while (lb <= ub) {
232             int mid = (lb + ub) / 2;
233             T r = (T) get(mid);
234             int compareResult = mComparator.compare(item, r);
235             if (compareResult == 0) {
236                 return mid;
237             } else if (compareResult > 0) {
238                 lb = mid + 1;
239             } else {
240                 ub = mid - 1;
241             }
242         }
243         return lb;
244     }
245 }