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.NonNull;
21bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.Nullable;
22bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.WorkerThread;
2324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
2424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikimport java.util.concurrent.Executor;
2524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
26e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikclass TiledPagedList<T> extends PagedList<T>
27e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        implements PagedStorage.Callback {
285dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    private final PositionalDataSource<T> mDataSource;
2924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
305dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
3167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // Creation thread for initial synchronous load, otherwise main thread
3267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // Safe to access main thread only state - no other thread has reference during construction
3367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        @AnyThread
34e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        @Override
35a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik        public void onPageResult(@PageResult.ResultType int type,
36a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                @NonNull PageResult<T> pageResult) {
375dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            if (pageResult.isInvalid()) {
38e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                detach();
39e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                return;
40e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
4124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
42e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (isDetached()) {
43e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                // No op, have detached
44e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                return;
45e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
4624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
4768d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik            if (type != PageResult.INIT && type != PageResult.TILE) {
4868d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik                throw new IllegalArgumentException("unexpected resultType" + type);
4968d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik            }
5068d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik
51e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (mStorage.getPageCount() == 0) {
525dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.initAndSplit(
53e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
545dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
55e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            } else {
565dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.insertPage(pageResult.positionOffset, pageResult.page,
57e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        TiledPagedList.this);
58e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
5967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
6067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            if (mBoundaryCallback != null) {
6167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boolean deferEmpty = mStorage.size() == 0;
625dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                boolean deferBegin = !deferEmpty
635dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.leadingNulls == 0
645dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.positionOffset == 0;
655dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                int size = size();
665dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                boolean deferEnd = !deferEmpty
675dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
685dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                                || (type == PageResult.TILE
699730bee03c505dbc095d75c5fe94555b06cb0a14Chris Craik                                        && (pageResult.positionOffset + mConfig.pageSize >= size)));
7067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
7167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            }
72e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        }
73e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    };
7424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
7524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @WorkerThread
765dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
7724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor mainThreadExecutor,
7824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor backgroundThreadExecutor,
7967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            @Nullable BoundaryCallback<T> boundaryCallback,
80e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @NonNull Config config,
8124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            int position) {
825dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
8367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boundaryCallback, config);
8424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mDataSource = dataSource;
8524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
86504f54d29f6cbff7a520880bd885304def99127dChris Craik        final int pageSize = mConfig.pageSize;
875dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        mLastLoad = position;
8824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (mDataSource.isInvalid()) {
905dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            detach();
915dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        } else {
925dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int firstLoadSize =
935dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
9467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
955dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int idealStart = position - firstLoadSize / 2;
965dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
9724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
98694588d1d059ac96142d6334ec7fce90abb7622bChris Craik            mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
99f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                    pageSize, mMainThreadExecutor, mReceiver);
1005dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
101e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
10224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
103e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
104e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    boolean isContiguous() {
105e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return false;
10624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
10724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
108ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    @NonNull
109ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    @Override
110ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    public DataSource<?, T> getDataSource() {
111ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        return mDataSource;
112ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    }
113ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik
114e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Nullable
11524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
116e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public Object getLastKey() {
117e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return mLastLoad;
11824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
11924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
12024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
121e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
122f0d13608aae3b4700d84c1c4532abbea56ea7a28Chris Craik            @NonNull Callback callback) {
123e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        //noinspection UnnecessaryLocalVariable
1245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
1255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
1265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (snapshot.isEmpty()
1275dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                || mStorage.size() != snapshot.size()) {
1285dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
1295dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    + " to be a snapshot of this PagedList");
1305dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
131e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
132e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // loop through each page and signal the callback for any pages that are present now,
133e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // but not in the snapshot.
134504f54d29f6cbff7a520880bd885304def99127dChris Craik        final int pageSize = mConfig.pageSize;
135e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
136e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int pageCount = mStorage.getPageCount();
137e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        for (int i = 0; i < pageCount; i++) {
138e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            int pageIndex = i + leadingNullPages;
139e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            int updatedPages = 0;
140e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            // count number of consecutive pages that were added since the snapshot...
141e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            while (updatedPages < mStorage.getPageCount()
142e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    && mStorage.hasPage(pageSize, pageIndex + updatedPages)
143e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
144e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                updatedPages++;
145e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
146e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            // and signal them all at once to the callback
147e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (updatedPages > 0) {
148e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
149e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                i += updatedPages - 1;
150ff8cbf69b414f94ab760260a7ab500f3b7da1e42Chris Craik            }
15124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
15224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
15324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
15424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
155e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    protected void loadAroundInternal(int index) {
156504f54d29f6cbff7a520880bd885304def99127dChris Craik        mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
15724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
15824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
159fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
160e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onInitialized(int count) {
161e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyInserted(0, count);
16224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
16324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
164fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
165e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePrepended(int leadingNulls, int changed, int added) {
166e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Contiguous callback on TiledPagedList");
16724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
168fd4fa4a65be59806d14e4625397948da008506b4Chris Craik
169fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
170e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPageAppended(int endPosition, int changed, int added) {
171e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Contiguous callback on TiledPagedList");
172e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
173e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
174e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
175e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePlaceholderInserted(final int pageIndex) {
176e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // placeholder means initialize a load
177e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        mBackgroundThreadExecutor.execute(new Runnable() {
178e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @Override
179e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            public void run() {
180e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                if (isDetached()) {
181e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    return;
182e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                }
183504f54d29f6cbff7a520880bd885304def99127dChris Craik                final int pageSize = mConfig.pageSize;
1845dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
1855dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                if (mDataSource.isInvalid()) {
1865dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    detach();
1875dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                } else {
1885dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    int startPosition = pageIndex * pageSize;
1895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    int count = Math.min(pageSize, mStorage.size() - startPosition);
190694588d1d059ac96142d6334ec7fce90abb7622bChris Craik                    mDataSource.dispatchLoadRange(
19168d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik                            PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver);
1925dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                }
193e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
194e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        });
195e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
196e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
197e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
198e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPageInserted(int start, int count) {
199e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyChanged(start, count);
200fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    }
20124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik}
202