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