mirror of https://github.com/M66B/FairEmail.git
659 lines
24 KiB
Java
659 lines
24 KiB
Java
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package androidx.paging;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import java.util.AbstractList;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Class holding the pages of data backing a PagedList, presenting sparse loaded data as a List.
|
|
* <p>
|
|
* It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds
|
|
* data, and does not have any notion of the ideas of async loads, or prefetching.
|
|
*/
|
|
final class PagedStorage<T> extends AbstractList<T> {
|
|
/**
|
|
* Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
|
|
* in that position is already loading. We use a singleton placeholder list that is distinct
|
|
* from Collections.emptyList() for safety.
|
|
*/
|
|
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
|
private static final List PLACEHOLDER_LIST = new ArrayList();
|
|
|
|
// Always set
|
|
private int mLeadingNullCount;
|
|
/**
|
|
* List of pages in storage.
|
|
*
|
|
* Two storage modes:
|
|
*
|
|
* Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
|
|
* Safe to access any item in any page.
|
|
*
|
|
* Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
|
|
* mPages may have nulls, or placeholder (empty) pages while content is loading.
|
|
*/
|
|
private final ArrayList<List<T>> mPages;
|
|
private int mTrailingNullCount;
|
|
|
|
private int mPositionOffset;
|
|
/**
|
|
* Number of loaded items held by {@link #mPages}. When tiling, doesn't count unloaded pages in
|
|
* {@link #mPages}. If tiling is disabled, same as {@link #mStorageCount}.
|
|
*
|
|
* This count is the one used for trimming.
|
|
*/
|
|
private int mLoadedCount;
|
|
|
|
/**
|
|
* Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
|
|
* {@link #mPages} may be null, but this value still counts them.
|
|
*/
|
|
private int mStorageCount;
|
|
|
|
// If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
|
|
private int mPageSize;
|
|
|
|
private int mNumberPrepended;
|
|
private int mNumberAppended;
|
|
|
|
PagedStorage() {
|
|
mLeadingNullCount = 0;
|
|
mPages = new ArrayList<>();
|
|
mTrailingNullCount = 0;
|
|
mPositionOffset = 0;
|
|
mLoadedCount = 0;
|
|
mStorageCount = 0;
|
|
mPageSize = 1;
|
|
mNumberPrepended = 0;
|
|
mNumberAppended = 0;
|
|
}
|
|
|
|
PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
|
|
this();
|
|
init(leadingNulls, page, trailingNulls, 0);
|
|
}
|
|
|
|
private PagedStorage(PagedStorage<T> other) {
|
|
mLeadingNullCount = other.mLeadingNullCount;
|
|
mPages = new ArrayList<>(other.mPages);
|
|
mTrailingNullCount = other.mTrailingNullCount;
|
|
mPositionOffset = other.mPositionOffset;
|
|
mLoadedCount = other.mLoadedCount;
|
|
mStorageCount = other.mStorageCount;
|
|
mPageSize = other.mPageSize;
|
|
mNumberPrepended = other.mNumberPrepended;
|
|
mNumberAppended = other.mNumberAppended;
|
|
}
|
|
|
|
PagedStorage<T> snapshot() {
|
|
return new PagedStorage<>(this);
|
|
}
|
|
|
|
private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
|
|
mLeadingNullCount = leadingNulls;
|
|
mPages.clear();
|
|
mPages.add(page);
|
|
mTrailingNullCount = trailingNulls;
|
|
|
|
mPositionOffset = positionOffset;
|
|
mLoadedCount = page.size();
|
|
mStorageCount = mLoadedCount;
|
|
|
|
// initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
|
|
// even if it will break if nulls convert.
|
|
mPageSize = page.size();
|
|
|
|
mNumberPrepended = 0;
|
|
mNumberAppended = 0;
|
|
}
|
|
|
|
void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
|
|
@NonNull Callback callback) {
|
|
init(leadingNulls, page, trailingNulls, positionOffset);
|
|
callback.onInitialized(size());
|
|
}
|
|
|
|
@Override
|
|
public T get(int i) {
|
|
if (i < 0 || i >= size()) {
|
|
throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
|
|
}
|
|
|
|
// is it definitely outside 'mPages'?
|
|
int localIndex = i - mLeadingNullCount;
|
|
if (localIndex < 0 || localIndex >= mStorageCount) {
|
|
return null;
|
|
}
|
|
|
|
int localPageIndex;
|
|
int pageInternalIndex;
|
|
|
|
if (isTiled()) {
|
|
// it's inside mPages, and we're tiled. Jump to correct tile.
|
|
localPageIndex = localIndex / mPageSize;
|
|
pageInternalIndex = localIndex % mPageSize;
|
|
} else {
|
|
// it's inside mPages, but page sizes aren't regular. Walk to correct tile.
|
|
// Pages can only be null while tiled, so accessing page count is safe.
|
|
pageInternalIndex = localIndex;
|
|
final int localPageCount = mPages.size();
|
|
for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
|
|
int pageSize = mPages.get(localPageIndex).size();
|
|
if (pageSize > pageInternalIndex) {
|
|
// stop, found the page
|
|
break;
|
|
}
|
|
pageInternalIndex -= pageSize;
|
|
}
|
|
}
|
|
|
|
List<T> page = mPages.get(localPageIndex);
|
|
if (page == null || page.size() == 0) {
|
|
// can only occur in tiled case, with untouched inner/placeholder pages
|
|
return null;
|
|
}
|
|
if (pageInternalIndex >= page.size()) {
|
|
eu.faircode.email.Log.e("PageStorage pageInternalIndex=" + pageInternalIndex + "/" + page.size());
|
|
return null;
|
|
}
|
|
return page.get(pageInternalIndex);
|
|
}
|
|
|
|
/**
|
|
* Returns true if all pages are the same size, except for the last, which may be smaller
|
|
*/
|
|
boolean isTiled() {
|
|
return mPageSize > 0;
|
|
}
|
|
|
|
int getLeadingNullCount() {
|
|
return mLeadingNullCount;
|
|
}
|
|
|
|
int getTrailingNullCount() {
|
|
return mTrailingNullCount;
|
|
}
|
|
|
|
int getStorageCount() {
|
|
return mStorageCount;
|
|
}
|
|
|
|
int getNumberAppended() {
|
|
return mNumberAppended;
|
|
}
|
|
|
|
int getNumberPrepended() {
|
|
return mNumberPrepended;
|
|
}
|
|
|
|
int getPageCount() {
|
|
return mPages.size();
|
|
}
|
|
|
|
int getLoadedCount() {
|
|
return mLoadedCount;
|
|
}
|
|
|
|
interface Callback {
|
|
void onInitialized(int count);
|
|
void onPagePrepended(int leadingNulls, int changed, int added);
|
|
void onPageAppended(int endPosition, int changed, int added);
|
|
void onPagePlaceholderInserted(int pageIndex);
|
|
void onPageInserted(int start, int count);
|
|
void onPagesRemoved(int startOfDrops, int count);
|
|
void onPagesSwappedToPlaceholder(int startOfDrops, int count);
|
|
void onEmptyPrepend();
|
|
void onEmptyAppend();
|
|
}
|
|
|
|
int getPositionOffset() {
|
|
return mPositionOffset;
|
|
}
|
|
|
|
int getMiddleOfLoadedRange() {
|
|
return mLeadingNullCount + mPositionOffset + mStorageCount / 2;
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return mLeadingNullCount + mStorageCount + mTrailingNullCount;
|
|
}
|
|
|
|
int computeLeadingNulls() {
|
|
int total = mLeadingNullCount;
|
|
final int pageCount = mPages.size();
|
|
for (int i = 0; i < pageCount; i++) {
|
|
List page = mPages.get(i);
|
|
if (page != null && page != PLACEHOLDER_LIST) {
|
|
break;
|
|
}
|
|
total += mPageSize;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int computeTrailingNulls() {
|
|
int total = mTrailingNullCount;
|
|
for (int i = mPages.size() - 1; i >= 0; i--) {
|
|
List page = mPages.get(i);
|
|
if (page != null && page != PLACEHOLDER_LIST) {
|
|
break;
|
|
}
|
|
total += mPageSize;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// ---------------- Trimming API -------------------
|
|
// Trimming is always done at the beginning or end of the list, as content is loaded.
|
|
// In addition to trimming pages in the storage, we also support pre-trimming pages (dropping
|
|
// them just before they're added) to avoid dispatching an add followed immediately by a trim.
|
|
//
|
|
// Note - we avoid trimming down to a single page to reduce chances of dropping page in
|
|
// viewport, since we don't strictly know the viewport. If trim is aggressively set to size of a
|
|
// single page, trimming while the user can see a page boundary is dangerous. To be safe, we
|
|
// just avoid trimming in these cases entirely.
|
|
|
|
private boolean needsTrim(int maxSize, int requiredRemaining, int localPageIndex) {
|
|
List<T> page = mPages.get(localPageIndex);
|
|
return page == null || (mLoadedCount > maxSize
|
|
&& mPages.size() > 2
|
|
&& page != PLACEHOLDER_LIST
|
|
&& mLoadedCount - page.size() >= requiredRemaining);
|
|
}
|
|
|
|
boolean needsTrimFromFront(int maxSize, int requiredRemaining) {
|
|
return needsTrim(maxSize, requiredRemaining, 0);
|
|
}
|
|
|
|
boolean needsTrimFromEnd(int maxSize, int requiredRemaining) {
|
|
return needsTrim(maxSize, requiredRemaining, mPages.size() - 1);
|
|
}
|
|
|
|
boolean shouldPreTrimNewPage(int maxSize, int requiredRemaining, int countToBeAdded) {
|
|
return mLoadedCount + countToBeAdded > maxSize
|
|
&& mPages.size() > 1
|
|
&& mLoadedCount >= requiredRemaining;
|
|
}
|
|
|
|
boolean trimFromFront(boolean insertNulls, int maxSize, int requiredRemaining,
|
|
@NonNull Callback callback) {
|
|
int totalRemoved = 0;
|
|
while (needsTrimFromFront(maxSize, requiredRemaining)) {
|
|
List page = mPages.remove(0);
|
|
int removed = (page == null) ? mPageSize : page.size();
|
|
totalRemoved += removed;
|
|
mStorageCount -= removed;
|
|
mLoadedCount -= (page == null) ? 0 : page.size();
|
|
}
|
|
|
|
if (totalRemoved > 0) {
|
|
if (insertNulls) {
|
|
// replace removed items with nulls
|
|
int previousLeadingNulls = mLeadingNullCount;
|
|
mLeadingNullCount += totalRemoved;
|
|
callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved);
|
|
} else {
|
|
// simply remove, and handle offset
|
|
mPositionOffset += totalRemoved;
|
|
callback.onPagesRemoved(mLeadingNullCount, totalRemoved);
|
|
}
|
|
}
|
|
return totalRemoved > 0;
|
|
}
|
|
|
|
boolean trimFromEnd(boolean insertNulls, int maxSize, int requiredRemaining,
|
|
@NonNull Callback callback) {
|
|
int totalRemoved = 0;
|
|
while (needsTrimFromEnd(maxSize, requiredRemaining)) {
|
|
List page = mPages.remove(mPages.size() - 1);
|
|
int removed = (page == null) ? mPageSize : page.size();
|
|
totalRemoved += removed;
|
|
mStorageCount -= removed;
|
|
mLoadedCount -= (page == null) ? 0 : page.size();
|
|
}
|
|
|
|
if (totalRemoved > 0) {
|
|
int newEndPosition = mLeadingNullCount + mStorageCount;
|
|
if (insertNulls) {
|
|
// replace removed items with nulls
|
|
mTrailingNullCount += totalRemoved;
|
|
callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved);
|
|
} else {
|
|
// items were just removed, signal
|
|
callback.onPagesRemoved(newEndPosition, totalRemoved);
|
|
}
|
|
}
|
|
return totalRemoved > 0;
|
|
}
|
|
|
|
// ---------------- Contiguous API -------------------
|
|
|
|
T getFirstLoadedItem() {
|
|
// safe to access first page's first item here:
|
|
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
|
|
return mPages.get(0).get(0);
|
|
}
|
|
|
|
T getLastLoadedItem() {
|
|
// safe to access last page's last item here:
|
|
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
|
|
List<T> page = mPages.get(mPages.size() - 1);
|
|
return page.get(page.size() - 1);
|
|
}
|
|
|
|
void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
|
|
final int count = page.size();
|
|
if (count == 0) {
|
|
// Nothing returned from source, stop loading in this direction
|
|
callback.onEmptyPrepend();
|
|
return;
|
|
}
|
|
if (mPageSize > 0 && count != mPageSize) {
|
|
if (mPages.size() == 1 && count > mPageSize) {
|
|
// prepending to a single item - update current page size to that of 'inner' page
|
|
mPageSize = count;
|
|
} else {
|
|
// no longer tiled
|
|
mPageSize = -1;
|
|
}
|
|
}
|
|
|
|
mPages.add(0, page);
|
|
mLoadedCount += count;
|
|
mStorageCount += count;
|
|
|
|
final int changedCount = Math.min(mLeadingNullCount, count);
|
|
final int addedCount = count - changedCount;
|
|
|
|
if (changedCount != 0) {
|
|
mLeadingNullCount -= changedCount;
|
|
}
|
|
mPositionOffset -= addedCount;
|
|
mNumberPrepended += count;
|
|
|
|
callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
|
|
}
|
|
|
|
void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
|
|
final int count = page.size();
|
|
if (count == 0) {
|
|
// Nothing returned from source, stop loading in this direction
|
|
callback.onEmptyAppend();
|
|
return;
|
|
}
|
|
|
|
if (mPageSize > 0) {
|
|
// if the previous page was smaller than mPageSize,
|
|
// or if this page is larger than the previous, disable tiling
|
|
if (mPages.get(mPages.size() - 1).size() != mPageSize
|
|
|| count > mPageSize) {
|
|
mPageSize = -1;
|
|
}
|
|
}
|
|
|
|
mPages.add(page);
|
|
mLoadedCount += count;
|
|
mStorageCount += count;
|
|
|
|
final int changedCount = Math.min(mTrailingNullCount, count);
|
|
final int addedCount = count - changedCount;
|
|
|
|
if (changedCount != 0) {
|
|
mTrailingNullCount -= changedCount;
|
|
}
|
|
mNumberAppended += count;
|
|
callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
|
|
changedCount, addedCount);
|
|
}
|
|
|
|
// ------------------ Non-Contiguous API (tiling required) ----------------------
|
|
|
|
/**
|
|
* Return true if the page at the passed position would be the first (if trimFromFront) or last
|
|
* page that's currently loading.
|
|
*/
|
|
boolean pageWouldBeBoundary(int positionOfPage, boolean trimFromFront) {
|
|
if (mPageSize < 1 || mPages.size() < 2) {
|
|
throw new IllegalStateException("Trimming attempt before sufficient load");
|
|
}
|
|
|
|
if (positionOfPage < mLeadingNullCount) {
|
|
// position represent page in leading nulls
|
|
return trimFromFront;
|
|
}
|
|
|
|
if (positionOfPage >= mLeadingNullCount + mStorageCount) {
|
|
// position represent page in trailing nulls
|
|
return !trimFromFront;
|
|
}
|
|
|
|
int localPageIndex = (positionOfPage - mLeadingNullCount) / mPageSize;
|
|
|
|
// walk outside in, return false if we find non-placeholder page before localPageIndex
|
|
if (trimFromFront) {
|
|
for (int i = 0; i < localPageIndex; i++) {
|
|
if (mPages.get(i) != null) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = mPages.size() - 1; i > localPageIndex; i--) {
|
|
if (mPages.get(i) != null) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// didn't find another page, so this one would be a boundary
|
|
return true;
|
|
}
|
|
|
|
void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
|
|
int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
|
|
|
|
int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
|
|
for (int i = 0; i < pageCount; i++) {
|
|
int beginInclusive = i * pageSize;
|
|
int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
|
|
|
|
List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
|
|
|
|
if (i == 0) {
|
|
// Trailing nulls for first page includes other pages in multiPageList
|
|
int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
|
|
init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
|
|
} else {
|
|
int insertPosition = leadingNulls + beginInclusive;
|
|
insertPage(insertPosition, sublist, null);
|
|
}
|
|
}
|
|
callback.onInitialized(size());
|
|
}
|
|
|
|
void tryInsertPageAndTrim(
|
|
int position,
|
|
@NonNull List<T> page,
|
|
int lastLoad,
|
|
int maxSize,
|
|
int requiredRemaining,
|
|
@NonNull Callback callback) {
|
|
boolean trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED;
|
|
boolean trimFromFront = lastLoad > getMiddleOfLoadedRange();
|
|
|
|
boolean pageInserted = !trim
|
|
|| !shouldPreTrimNewPage(maxSize, requiredRemaining, page.size())
|
|
|| !pageWouldBeBoundary(position, trimFromFront);
|
|
|
|
if (pageInserted) {
|
|
insertPage(position, page, callback);
|
|
} else {
|
|
// trim would have us drop the page we just loaded - swap it to null
|
|
int localPageIndex = (position - mLeadingNullCount) / mPageSize;
|
|
mPages.set(localPageIndex, null);
|
|
|
|
// note: we also remove it, so we don't have to guess how large a 'null' page is later
|
|
mStorageCount -= page.size();
|
|
if (trimFromFront) {
|
|
mPages.remove(0);
|
|
mLeadingNullCount += page.size();
|
|
} else {
|
|
mPages.remove(mPages.size() - 1);
|
|
mTrailingNullCount += page.size();
|
|
}
|
|
}
|
|
|
|
if (trim) {
|
|
if (trimFromFront) {
|
|
trimFromFront(true, maxSize, requiredRemaining, callback);
|
|
} else {
|
|
trimFromEnd(true, maxSize, requiredRemaining, callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
|
|
final int newPageSize = page.size();
|
|
if (newPageSize != mPageSize) {
|
|
// differing page size is OK in 2 cases, when the page is being added:
|
|
// 1) to the end (in which case, ignore new smaller size)
|
|
// 2) only the last page has been added so far (in which case, adopt new bigger size)
|
|
|
|
int size = size();
|
|
boolean addingLastPage = position == (size - size % mPageSize)
|
|
&& newPageSize < mPageSize;
|
|
boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
|
|
&& newPageSize > mPageSize;
|
|
|
|
// OK only if existing single page, and it's the last one
|
|
if (!onlyEndPagePresent && !addingLastPage) {
|
|
//throw new IllegalArgumentException("page introduces incorrect tiling");
|
|
eu.faircode.email.Log.e("PagedStorage tiling" +
|
|
" size=" + newPageSize + "/" + mPageSize +
|
|
" addingLastPage=" + addingLastPage +
|
|
" onlyEndPagePresent=" + onlyEndPagePresent);
|
|
return;
|
|
}
|
|
if (onlyEndPagePresent) {
|
|
mPageSize = newPageSize;
|
|
}
|
|
}
|
|
|
|
int pageIndex = position / mPageSize;
|
|
|
|
allocatePageRange(pageIndex, pageIndex);
|
|
|
|
int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
|
|
|
|
List<T> oldPage = mPages.get(localPageIndex);
|
|
if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
|
|
throw new IllegalArgumentException(
|
|
"Invalid position " + position + ": data already loaded");
|
|
}
|
|
mPages.set(localPageIndex, page);
|
|
mLoadedCount += newPageSize;
|
|
if (callback != null) {
|
|
callback.onPageInserted(position, newPageSize);
|
|
}
|
|
}
|
|
|
|
void allocatePageRange(final int minimumPage, final int maximumPage) {
|
|
int leadingNullPages = mLeadingNullCount / mPageSize;
|
|
|
|
if (minimumPage < leadingNullPages) {
|
|
for (int i = 0; i < leadingNullPages - minimumPage; i++) {
|
|
mPages.add(0, null);
|
|
}
|
|
int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
|
|
mStorageCount += newStorageAllocated;
|
|
mLeadingNullCount -= newStorageAllocated;
|
|
|
|
leadingNullPages = minimumPage;
|
|
}
|
|
if (maximumPage >= leadingNullPages + mPages.size()) {
|
|
int newStorageAllocated = Math.min(mTrailingNullCount,
|
|
(maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
|
|
for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
|
|
mPages.add(mPages.size(), null);
|
|
}
|
|
mStorageCount += newStorageAllocated;
|
|
mTrailingNullCount -= newStorageAllocated;
|
|
}
|
|
}
|
|
|
|
public void allocatePlaceholders(int index, int prefetchDistance,
|
|
int pageSize, Callback callback) {
|
|
if (pageSize != mPageSize) {
|
|
if (pageSize < mPageSize) {
|
|
throw new IllegalArgumentException("Page size cannot be reduced");
|
|
}
|
|
if (mPages.size() != 1 || mTrailingNullCount != 0) {
|
|
// not in single, last page allocated case - can't change page size
|
|
throw new IllegalArgumentException(
|
|
"Page size can change only if last page is only one present");
|
|
}
|
|
mPageSize = pageSize;
|
|
}
|
|
|
|
final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
|
|
int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
|
|
int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
|
|
|
|
allocatePageRange(minimumPage, maximumPage);
|
|
int leadingNullPages = mLeadingNullCount / mPageSize;
|
|
for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
|
|
int localPageIndex = pageIndex - leadingNullPages;
|
|
if (mPages.get(localPageIndex) == null) {
|
|
//noinspection unchecked
|
|
mPages.set(localPageIndex, PLACEHOLDER_LIST);
|
|
callback.onPagePlaceholderInserted(pageIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean hasPage(int pageSize, int index) {
|
|
// NOTE: we pass pageSize here to avoid in case mPageSize
|
|
// not fully initialized (when last page only one loaded)
|
|
int leadingNullPages = mLeadingNullCount / pageSize;
|
|
|
|
if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
|
|
return false;
|
|
}
|
|
|
|
List<T> page = mPages.get(index - leadingNullPages);
|
|
|
|
return page != null && page != PLACEHOLDER_LIST;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
|
|
+ ", storage " + mStorageCount
|
|
+ ", trailing " + getTrailingNullCount());
|
|
|
|
for (int i = 0; i < mPages.size(); i++) {
|
|
ret.append(" ").append(mPages.get(i));
|
|
}
|
|
return ret.toString();
|
|
}
|
|
}
|