1/* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.paging; 18 19import androidx.annotation.AnyThread; 20import androidx.annotation.NonNull; 21import androidx.annotation.Nullable; 22import androidx.annotation.WorkerThread; 23 24import java.util.concurrent.Executor; 25 26class TiledPagedList<T> extends PagedList<T> 27 implements PagedStorage.Callback { 28 private final PositionalDataSource<T> mDataSource; 29 30 private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() { 31 // Creation thread for initial synchronous load, otherwise main thread 32 // Safe to access main thread only state - no other thread has reference during construction 33 @AnyThread 34 @Override 35 public void onPageResult(@PageResult.ResultType int type, 36 @NonNull PageResult<T> pageResult) { 37 if (pageResult.isInvalid()) { 38 detach(); 39 return; 40 } 41 42 if (isDetached()) { 43 // No op, have detached 44 return; 45 } 46 47 if (type != PageResult.INIT && type != PageResult.TILE) { 48 throw new IllegalArgumentException("unexpected resultType" + type); 49 } 50 51 if (mStorage.getPageCount() == 0) { 52 mStorage.initAndSplit( 53 pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls, 54 pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this); 55 } else { 56 mStorage.insertPage(pageResult.positionOffset, pageResult.page, 57 TiledPagedList.this); 58 } 59 60 if (mBoundaryCallback != null) { 61 boolean deferEmpty = mStorage.size() == 0; 62 boolean deferBegin = !deferEmpty 63 && pageResult.leadingNulls == 0 64 && pageResult.positionOffset == 0; 65 int size = size(); 66 boolean deferEnd = !deferEmpty 67 && ((type == PageResult.INIT && pageResult.trailingNulls == 0) 68 || (type == PageResult.TILE 69 && (pageResult.positionOffset + mConfig.pageSize >= size))); 70 deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); 71 } 72 } 73 }; 74 75 @WorkerThread 76 TiledPagedList(@NonNull PositionalDataSource<T> dataSource, 77 @NonNull Executor mainThreadExecutor, 78 @NonNull Executor backgroundThreadExecutor, 79 @Nullable BoundaryCallback<T> boundaryCallback, 80 @NonNull Config config, 81 int position) { 82 super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor, 83 boundaryCallback, config); 84 mDataSource = dataSource; 85 86 final int pageSize = mConfig.pageSize; 87 mLastLoad = position; 88 89 if (mDataSource.isInvalid()) { 90 detach(); 91 } else { 92 final int firstLoadSize = 93 (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize; 94 95 final int idealStart = position - firstLoadSize / 2; 96 final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize); 97 98 mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize, 99 pageSize, mMainThreadExecutor, mReceiver); 100 } 101 } 102 103 @Override 104 boolean isContiguous() { 105 return false; 106 } 107 108 @NonNull 109 @Override 110 public DataSource<?, T> getDataSource() { 111 return mDataSource; 112 } 113 114 @Nullable 115 @Override 116 public Object getLastKey() { 117 return mLastLoad; 118 } 119 120 @Override 121 protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot, 122 @NonNull Callback callback) { 123 //noinspection UnnecessaryLocalVariable 124 final PagedStorage<T> snapshot = pagedListSnapshot.mStorage; 125 126 if (snapshot.isEmpty() 127 || mStorage.size() != snapshot.size()) { 128 throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" 129 + " to be a snapshot of this PagedList"); 130 } 131 132 // loop through each page and signal the callback for any pages that are present now, 133 // but not in the snapshot. 134 final int pageSize = mConfig.pageSize; 135 final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize; 136 final int pageCount = mStorage.getPageCount(); 137 for (int i = 0; i < pageCount; i++) { 138 int pageIndex = i + leadingNullPages; 139 int updatedPages = 0; 140 // count number of consecutive pages that were added since the snapshot... 141 while (updatedPages < mStorage.getPageCount() 142 && mStorage.hasPage(pageSize, pageIndex + updatedPages) 143 && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) { 144 updatedPages++; 145 } 146 // and signal them all at once to the callback 147 if (updatedPages > 0) { 148 callback.onChanged(pageIndex * pageSize, pageSize * updatedPages); 149 i += updatedPages - 1; 150 } 151 } 152 } 153 154 @Override 155 protected void loadAroundInternal(int index) { 156 mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this); 157 } 158 159 @Override 160 public void onInitialized(int count) { 161 notifyInserted(0, count); 162 } 163 164 @Override 165 public void onPagePrepended(int leadingNulls, int changed, int added) { 166 throw new IllegalStateException("Contiguous callback on TiledPagedList"); 167 } 168 169 @Override 170 public void onPageAppended(int endPosition, int changed, int added) { 171 throw new IllegalStateException("Contiguous callback on TiledPagedList"); 172 } 173 174 @Override 175 public void onPagePlaceholderInserted(final int pageIndex) { 176 // placeholder means initialize a load 177 mBackgroundThreadExecutor.execute(new Runnable() { 178 @Override 179 public void run() { 180 if (isDetached()) { 181 return; 182 } 183 final int pageSize = mConfig.pageSize; 184 185 if (mDataSource.isInvalid()) { 186 detach(); 187 } else { 188 int startPosition = pageIndex * pageSize; 189 int count = Math.min(pageSize, mStorage.size() - startPosition); 190 mDataSource.dispatchLoadRange( 191 PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver); 192 } 193 } 194 }); 195 } 196 197 @Override 198 public void onPageInserted(int start, int count) { 199 notifyChanged(start, count); 200 } 201} 202