124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik/*
2bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette * Copyright 2018 The Android Open Source Project
324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *
424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Licensed under the Apache License, Version 2.0 (the "License");
524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * you may not use this file except in compliance with the License.
624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * You may obtain a copy of the License at
724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *
824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *      http://www.apache.org/licenses/LICENSE-2.0
924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *
1024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Unless required by applicable law or agreed to in writing, software
1124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * distributed under the License is distributed on an "AS IS" BASIS,
1224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * See the License for the specific language governing permissions and
1424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * limitations under the License.
1524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik */
1624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
17bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverettepackage androidx.paging;
1824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
19bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.AnyThread;
20bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.MainThread;
21bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.NonNull;
22bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.Nullable;
2324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craikimport java.util.List;
2524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikimport java.util.concurrent.Executor;
2624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
27e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikclass ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
28e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    private final ContiguousDataSource<K, V> mDataSource;
2924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private boolean mPrependWorkerRunning = false;
3024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private boolean mAppendWorkerRunning = false;
3124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
3224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private int mPrependItemsRequested = 0;
3324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private int mAppendItemsRequested = 0;
3424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
355dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
3667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // Creation thread for initial synchronous load, otherwise main thread
3767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // Safe to access main thread only state - no other thread has reference during construction
3867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        @AnyThread
39e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        @Override
40a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik        public void onPageResult(@PageResult.ResultType int resultType,
41a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                @NonNull PageResult<V> pageResult) {
425dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            if (pageResult.isInvalid()) {
43e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                detach();
44e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                return;
45e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
4624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
47e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (isDetached()) {
48e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                // No op, have detached
49e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                return;
50e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
5124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
525dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            List<V> page = pageResult.page;
53a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik            if (resultType == PageResult.INIT) {
545dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
55e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        pageResult.positionOffset, ContiguousPagedList.this);
56e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
57e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                    // Because the ContiguousPagedList wasn't initialized with a last load position,
58e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                    // initialize it to the middle of the initial load
59e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                    mLastLoad =
60e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                            pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
61e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                }
62a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik            } else if (resultType == PageResult.APPEND) {
635dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.appendPage(page, ContiguousPagedList.this);
64a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik            } else if (resultType == PageResult.PREPEND) {
655dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.prependPage(page, ContiguousPagedList.this);
6668d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik            } else {
6768d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik                throw new IllegalArgumentException("unexpected resultType " + resultType);
68e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
6967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
7068d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik
7167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            if (mBoundaryCallback != null) {
7267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boolean deferEmpty = mStorage.size() == 0;
7367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boolean deferBegin = !deferEmpty
74a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                        && resultType == PageResult.PREPEND
755dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.page.size() == 0;
7667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boolean deferEnd = !deferEmpty
77a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                        && resultType == PageResult.APPEND
785dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.page.size() == 0;
7967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
8067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            }
81e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        }
82e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    };
83e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
84e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    static final int LAST_LOAD_UNSPECIFIED = -1;
85e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik
86e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    ContiguousPagedList(
87e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @NonNull ContiguousDataSource<K, V> dataSource,
8824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor mainThreadExecutor,
8924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor backgroundThreadExecutor,
9067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            @Nullable BoundaryCallback<V> boundaryCallback,
91e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @NonNull Config config,
92e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik            final @Nullable K key,
93e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik            int lastLoad) {
945dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
9567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boundaryCallback, config);
9624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mDataSource = dataSource;
97e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        mLastLoad = lastLoad;
9824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
995dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (mDataSource.isInvalid()) {
1005dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            detach();
1015dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        } else {
102694588d1d059ac96142d6334ec7fce90abb7622bChris Craik            mDataSource.dispatchLoadInitial(key,
1035dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    mConfig.initialLoadSizeHint,
104f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                    mConfig.pageSize,
1055dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    mConfig.enablePlaceholders,
106f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                    mMainThreadExecutor,
107f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                    mReceiver);
1085dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
10924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
11024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
111e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
11224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
113e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    void dispatchUpdatesSinceSnapshot(
114e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
1155dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
116e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
117e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
118e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
119e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
120e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int previousTrailing = snapshot.getTrailingNullCount();
121e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int previousLeading = snapshot.getLeadingNullCount();
122e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
123e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // Validate that the snapshot looks like a previous version of this list - if it's not,
124e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // we can't be sure we'll dispatch callbacks safely
1255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (snapshot.isEmpty()
1265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                || newlyAppended < 0
127e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                || newlyPrepended < 0
128e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
129e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
130e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                || (mStorage.getStorageCount()
131e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
132e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
133e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    + " to be a snapshot of this PagedList");
134e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        }
135e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
136e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        if (newlyAppended != 0) {
137e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            final int changedCount = Math.min(previousTrailing, newlyAppended);
138e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            final int addedCount = newlyAppended - changedCount;
139e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
140e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
141e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (changedCount != 0) {
142e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onChanged(endPosition, changedCount);
143e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
144e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (addedCount != 0) {
145e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onInserted(endPosition + changedCount, addedCount);
146e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
147e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        }
148e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        if (newlyPrepended != 0) {
149e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            final int changedCount = Math.min(previousLeading, newlyPrepended);
150e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            final int addedCount = newlyPrepended - changedCount;
151e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
152e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (changedCount != 0) {
153e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onChanged(previousLeading, changedCount);
154e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
155e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (addedCount != 0) {
156e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onInserted(0, addedCount);
157e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
1582e9d5136685b07ef5bfabcd3936b1eedb5d24e91Chris Craik        }
1592e9d5136685b07ef5bfabcd3936b1eedb5d24e91Chris Craik    }
1602e9d5136685b07ef5bfabcd3936b1eedb5d24e91Chris Craik
161e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
1622e9d5136685b07ef5bfabcd3936b1eedb5d24e91Chris Craik    @Override
163e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    protected void loadAroundInternal(int index) {
164504f54d29f6cbff7a520880bd885304def99127dChris Craik        int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
165504f54d29f6cbff7a520880bd885304def99127dChris Craik        int appendItems = index + mConfig.prefetchDistance
166e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
16724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
168114bdca94b6571ef9f45ea6e826715141741d49eChris Craik        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
169114bdca94b6571ef9f45ea6e826715141741d49eChris Craik        if (mPrependItemsRequested > 0) {
17024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            schedulePrepend();
17124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
172114bdca94b6571ef9f45ea6e826715141741d49eChris Craik
173114bdca94b6571ef9f45ea6e826715141741d49eChris Craik        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
174114bdca94b6571ef9f45ea6e826715141741d49eChris Craik        if (mAppendItemsRequested > 0) {
17524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            scheduleAppend();
17624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
17724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
17824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
17924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @MainThread
18024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private void schedulePrepend() {
18124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        if (mPrependWorkerRunning) {
18224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            return;
18324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
18424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mPrependWorkerRunning = true;
18524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
186e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
187e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
188e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // safe to access first item here - mStorage can't be empty if we're prepending
18967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        final V item = mStorage.getFirstLoadedItem();
19024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mBackgroundThreadExecutor.execute(new Runnable() {
19124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @Override
19224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            public void run() {
193e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                if (isDetached()) {
19424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik                    return;
19524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik                }
1965dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                if (mDataSource.isInvalid()) {
1975dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    detach();
1985dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                } else {
199694588d1d059ac96142d6334ec7fce90abb7622bChris Craik                    mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
200f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                            mMainThreadExecutor, mReceiver);
2015dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                }
2025dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
20324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            }
20424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        });
20524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
20624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
20724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @MainThread
20824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    private void scheduleAppend() {
20924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        if (mAppendWorkerRunning) {
21024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            return;
21124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
21224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mAppendWorkerRunning = true;
21324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
214e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int position = mStorage.getLeadingNullCount()
215e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
216e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
217e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // safe to access first item here - mStorage can't be empty if we're appending
21867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        final V item = mStorage.getLastLoadedItem();
21924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mBackgroundThreadExecutor.execute(new Runnable() {
22024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @Override
22124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            public void run() {
222e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                if (isDetached()) {
22324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik                    return;
22424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik                }
2255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                if (mDataSource.isInvalid()) {
2265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    detach();
2275dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                } else {
228694588d1d059ac96142d6334ec7fce90abb7622bChris Craik                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
229f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                            mMainThreadExecutor, mReceiver);
2305dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                }
23124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            }
23224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        });
23324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
23424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
235e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
236e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    boolean isContiguous() {
237e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return true;
238e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
23924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
240ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    @NonNull
241ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    @Override
242ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    public DataSource<?, V> getDataSource() {
243ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        return mDataSource;
244ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    }
245ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik
246e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Nullable
247e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
248e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public Object getLastKey() {
249e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return mDataSource.getKey(mLastLoad, mLastItem);
250e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
25124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
252e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
253e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
254e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onInitialized(int count) {
255e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyInserted(0, count);
256e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
25724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
258e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
259e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
260e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
261e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // consider whether to post more work, now that a page is fully prepended
262e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
26324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mPrependWorkerRunning = false;
26424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        if (mPrependItemsRequested > 0) {
26524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            // not done prepending, keep going
26624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            schedulePrepend();
26724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
26824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
26924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        // finally dispatch callbacks, after prepend may have already been scheduled
270e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyChanged(leadingNulls, changedCount);
271e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyInserted(0, addedCount);
27267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
27367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        offsetBoundaryAccessIndices(addedCount);
27424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
27524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
27624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @MainThread
277e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
278e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPageAppended(int endPosition, int changedCount, int addedCount) {
279e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // consider whether to post more work, now that a page is fully appended
28024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
281e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
28224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mAppendWorkerRunning = false;
28324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        if (mAppendItemsRequested > 0) {
28424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            // not done appending, keep going
28524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            scheduleAppend();
28624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
28724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
28824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        // finally dispatch callbacks, after append may have already been scheduled
289e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyChanged(endPosition, changedCount);
290e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyInserted(endPosition + changedCount, addedCount);
29124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
29224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
293e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
2942e9d5136685b07ef5bfabcd3936b1eedb5d24e91Chris Craik    @Override
295e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePlaceholderInserted(int pageIndex) {
296e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
29724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
29824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
299e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @MainThread
300fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
301e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPageInserted(int start, int count) {
302e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
303fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    }
30424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik}
305