TiledPagedList.java revision a682a75615b3347502e1193a8e5b566a8edb5893
124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik/*
224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Copyright (C) 2017 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
17ef346ae131affbba6345e00d833103acc5743c8aChris Craikpackage android.arch.paging;
1824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
19e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikimport android.support.annotation.AnyThread;
2024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikimport android.support.annotation.NonNull;
2124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikimport android.support.annotation.Nullable;
2224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikimport android.support.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
47e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (mStorage.getPageCount() == 0) {
485dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.initAndSplit(
49e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
505dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
51e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            } else {
525dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                mStorage.insertPage(pageResult.positionOffset, pageResult.page,
53e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                        TiledPagedList.this);
54e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
5567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
5667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            if (mBoundaryCallback != null) {
5767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boolean deferEmpty = mStorage.size() == 0;
585dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                boolean deferBegin = !deferEmpty
595dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.leadingNulls == 0
605dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && pageResult.positionOffset == 0;
615dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                int size = size();
625dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                boolean deferEnd = !deferEmpty
635dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                        && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
645dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                                || (type == PageResult.TILE
655dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                                        && pageResult.positionOffset
665dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                                                == (size - size % mConfig.pageSize)));
6767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
6867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            }
69e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        }
70e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    };
7124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
7224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @WorkerThread
735dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
7424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor mainThreadExecutor,
7524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            @NonNull Executor backgroundThreadExecutor,
7667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            @Nullable BoundaryCallback<T> boundaryCallback,
77e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @NonNull Config config,
7824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik            int position) {
795dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
8067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                boundaryCallback, config);
8124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        mDataSource = dataSource;
8224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
83504f54d29f6cbff7a520880bd885304def99127dChris Craik        final int pageSize = mConfig.pageSize;
845dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        mLastLoad = position;
8524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
865dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (mDataSource.isInvalid()) {
875dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            detach();
885dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        } else {
895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int firstLoadSize =
905dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
9167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
925dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int idealStart = position - firstLoadSize / 2;
935dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
9424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
95a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik            DataSource.InitialLoadCallback<T> callback = new DataSource.InitialLoadCallback<>(
96a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                            DataSource.LOAD_COUNT_REQUIRED_TILED,
97a682a75615b3347502e1193a8e5b566a8edb5893Chris Craik                    mConfig.pageSize, mDataSource, mReceiver);
985dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            mDataSource.loadInitial(roundedPageStart, firstLoadSize, pageSize, callback);
99ff8cbf69b414f94ab760260a7ab500f3b7da1e42Chris Craik
1005dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            // If initialLoad's callback is not called within the body, we force any following calls
1015dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            // to post to the UI thread. This constructor may be run on a background thread, but
1025dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            // after constructor, mutation must happen on UI thread.
1035dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            callback.setPostExecutor(mMainThreadExecutor);
1045dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
105e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
10624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
107e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
108e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    boolean isContiguous() {
109e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return false;
11024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
11124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
112e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Nullable
11324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
114e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public Object getLastKey() {
115e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        return mLastLoad;
11624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
11724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
11824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
119e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
120f0d13608aae3b4700d84c1c4532abbea56ea7a28Chris Craik            @NonNull Callback callback) {
121e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        //noinspection UnnecessaryLocalVariable
1225dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
1235dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
1245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        if (snapshot.isEmpty()
1255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                || mStorage.size() != snapshot.size()) {
1265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
1275dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    + " to be a snapshot of this PagedList");
1285dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
129e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
130e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // loop through each page and signal the callback for any pages that are present now,
131e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // but not in the snapshot.
132504f54d29f6cbff7a520880bd885304def99127dChris Craik        final int pageSize = mConfig.pageSize;
133e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
134e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        final int pageCount = mStorage.getPageCount();
135e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        for (int i = 0; i < pageCount; i++) {
136e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            int pageIndex = i + leadingNullPages;
137e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            int updatedPages = 0;
138e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            // count number of consecutive pages that were added since the snapshot...
139e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            while (updatedPages < mStorage.getPageCount()
140e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    && mStorage.hasPage(pageSize, pageIndex + updatedPages)
141e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
142e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                updatedPages++;
143e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            }
144e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            // and signal them all at once to the callback
145e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            if (updatedPages > 0) {
146e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
147e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                i += updatedPages - 1;
148ff8cbf69b414f94ab760260a7ab500f3b7da1e42Chris Craik            }
14924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik        }
15024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
15124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
15224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    @Override
153e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    protected void loadAroundInternal(int index) {
154504f54d29f6cbff7a520880bd885304def99127dChris Craik        mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
15524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
15624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
157fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
158e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onInitialized(int count) {
159e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        notifyInserted(0, count);
16024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
16124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
162fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
163e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePrepended(int leadingNulls, int changed, int added) {
164e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Contiguous callback on TiledPagedList");
16524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    }
166fd4fa4a65be59806d14e4625397948da008506b4Chris Craik
167fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
168e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPageAppended(int endPosition, int changed, int added) {
169e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        throw new IllegalStateException("Contiguous callback on TiledPagedList");
170e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    }
171e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik
172e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    @Override
173e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik    public void onPagePlaceholderInserted(final int pageIndex) {
174e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        // placeholder means initialize a load
175e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik        mBackgroundThreadExecutor.execute(new Runnable() {
176e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            @Override
177e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik            public void run() {
178e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                if (isDetached()) {
179e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                    return;
180e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik                }
181504f54d29f6cbff7a520880bd885304def99127dChris Craik                final int pageSize = mConfig.pageSize;
1825dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
1835dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                if (mDataSource.isInvalid()) {
1845dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    detach();
1855dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                } else {
1865dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    int startPosition = pageIndex * pageSize;
1875dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    int count = Math.min(pageSize, mStorage.size() - startPosition);
1885dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    DataSource.LoadCallback<T> callback = new DataSource.LoadCallback<>(
1895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                            PageResult.TILE, mMainThreadExecutor, mDataSource, mReceiver);
1905dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    callback.setPositionOffset(startPosition);
1915dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                    mDataSource.loadRange(startPosition, count, callback);
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