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 }