blob: 42eb320da101a3da405c0f13c09dd5d389c8fc1b [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.arch.paging;
import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
import java.util.concurrent.Executor;
class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
private final ContiguousDataSource<K, V> mDataSource;
private boolean mPrependWorkerRunning = false;
private boolean mAppendWorkerRunning = false;
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
public void onPageResult(@PageResult.ResultType int resultType,
@NonNull PageResult<V> pageResult) {
if (pageResult.isInvalid()) {
detach();
return;
}
if (isDetached()) {
// No op, have detached
return;
}
List<V> page = pageResult.page;
if (resultType == PageResult.INIT) {
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
} else if (resultType == PageResult.APPEND) {
mStorage.appendPage(page, ContiguousPagedList.this);
} else if (resultType == PageResult.PREPEND) {
mStorage.prependPage(page, ContiguousPagedList.this);
}
if (mBoundaryCallback != null) {
boolean deferEmpty = mStorage.size() == 0;
boolean deferBegin = !deferEmpty
&& resultType == PageResult.PREPEND
&& pageResult.page.size() == 0;
boolean deferEnd = !deferEmpty
&& resultType == PageResult.APPEND
&& pageResult.page.size() == 0;
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
};
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
}
@MainThread
@Override
void dispatchUpdatesSinceSnapshot(
@NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
final int previousTrailing = snapshot.getTrailingNullCount();
final int previousLeading = snapshot.getLeadingNullCount();
// Validate that the snapshot looks like a previous version of this list - if it's not,
// we can't be sure we'll dispatch callbacks safely
if (snapshot.isEmpty()
|| newlyAppended < 0
|| newlyPrepended < 0
|| mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
|| mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
|| (mStorage.getStorageCount()
!= snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ " to be a snapshot of this PagedList");
}
if (newlyAppended != 0) {
final int changedCount = Math.min(previousTrailing, newlyAppended);
final int addedCount = newlyAppended - changedCount;
final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
if (changedCount != 0) {
callback.onChanged(endPosition, changedCount);
}
if (addedCount != 0) {
callback.onInserted(endPosition + changedCount, addedCount);
}
}
if (newlyPrepended != 0) {
final int changedCount = Math.min(previousLeading, newlyPrepended);
final int addedCount = newlyPrepended - changedCount;
if (changedCount != 0) {
callback.onChanged(previousLeading, changedCount);
}
if (addedCount != 0) {
callback.onInserted(0, addedCount);
}
}
}
@MainThread
@Override
protected void loadAroundInternal(int index) {
int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
int appendItems = index + mConfig.prefetchDistance
- (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
schedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
if (mAppendItemsRequested > 0) {
scheduleAppend();
}
}
@MainThread
private void schedulePrepend() {
if (mPrependWorkerRunning) {
return;
}
mPrependWorkerRunning = true;
final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're prepending
final V item = mStorage.getFirstLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}
@MainThread
private void scheduleAppend() {
if (mAppendWorkerRunning) {
return;
}
mAppendWorkerRunning = true;
final int position = mStorage.getLeadingNullCount()
+ mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're appending
final V item = mStorage.getLastLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
}
@Override
boolean isContiguous() {
return true;
}
@Nullable
@Override
public Object getLastKey() {
return mDataSource.getKey(mLastLoad, mLastItem);
}
@MainThread
@Override
public void onInitialized(int count) {
notifyInserted(0, count);
}
@MainThread
@Override
public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
// consider whether to post more work, now that a page is fully prepended
mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
mPrependWorkerRunning = false;
if (mPrependItemsRequested > 0) {
// not done prepending, keep going
schedulePrepend();
}
// finally dispatch callbacks, after prepend may have already been scheduled
notifyChanged(leadingNulls, changedCount);
notifyInserted(0, addedCount);
offsetBoundaryAccessIndices(addedCount);
}
@MainThread
@Override
public void onPageAppended(int endPosition, int changedCount, int addedCount) {
// consider whether to post more work, now that a page is fully appended
mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
scheduleAppend();
}
// finally dispatch callbacks, after append may have already been scheduled
notifyChanged(endPosition, changedCount);
notifyInserted(endPosition + changedCount, addedCount);
}
@MainThread
@Override
public void onPagePlaceholderInserted(int pageIndex) {
throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
@MainThread
@Override
public void onPageInserted(int start, int count) {
throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
}