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