1 /* <lambda>null2 * Copyright (C) 2024 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 17 package com.android.photopicker.data.paging 18 19 import android.net.Uri 20 import androidx.paging.PagingSource 21 import androidx.paging.PagingSource.LoadParams 22 import androidx.paging.PagingSource.LoadResult 23 import androidx.paging.PagingState 24 import com.android.photopicker.data.model.Media 25 import com.android.photopicker.data.model.MediaPageKey 26 import com.android.photopicker.data.model.MediaSource 27 import java.time.LocalDateTime 28 import java.time.ZoneOffset 29 import java.time.temporal.ChronoUnit 30 31 /** 32 * This [FakeInMemoryMediaPagingSource] class is responsible to providing paginated media data from 33 * Picker Database by serving requests from Paging library. 34 * 35 * It generates and returns its own fake data. 36 */ 37 class FakeInMemoryMediaPagingSource 38 private constructor(val DATA_SIZE: Int = DEFAULT_SIZE, private val DATA_LIST: List<Media>? = null) : 39 PagingSource<MediaPageKey, Media>() { 40 41 companion object { 42 const val DEFAULT_SIZE = 1_000 43 } 44 45 constructor(dataSize: Int = DEFAULT_SIZE) : this(dataSize, null) 46 47 constructor(dataList: List<Media>) : this(DEFAULT_SIZE, dataList) 48 49 private val currentDateTime = LocalDateTime.now() 50 51 // If a [DATA_LIST] was provided, use it, otherwise generate a list of the requested size. 52 val DATA = 53 DATA_LIST 54 ?: buildList<Media>() { 55 for (i in 1..DATA_SIZE) { 56 add( 57 Media.Image( 58 mediaId = "$i", 59 pickerId = i.toLong(), 60 authority = "a", 61 mediaSource = MediaSource.LOCAL, 62 mediaUri = 63 Uri.EMPTY.buildUpon() 64 .apply { 65 scheme("content") 66 authority("media") 67 path("picker") 68 path("a") 69 path("$i") 70 } 71 .build(), 72 glideLoadableUri = 73 Uri.EMPTY.buildUpon() 74 .apply { 75 scheme("content") 76 authority("a") 77 path("$i") 78 } 79 .build(), 80 dateTakenMillisLong = 81 currentDateTime 82 .minus(i.toLong(), ChronoUnit.DAYS) 83 .toEpochSecond(ZoneOffset.UTC) * 1000, 84 sizeInBytes = 1000L, 85 mimeType = "image/png", 86 standardMimeTypeExtension = 1, 87 ) 88 ) 89 } 90 } 91 92 override suspend fun load(params: LoadParams<MediaPageKey>): LoadResult<MediaPageKey, Media> { 93 94 // Handle a data size of 0 for the first page, and return an empty page with no further 95 // keys. 96 if (DATA_SIZE == 0 && params.key == null) { 97 return LoadResult.Page(data = emptyList(), nextKey = null, prevKey = null) 98 } 99 100 // This is inefficient, but a reliable way to locate the record being requested by the 101 // [MediaPageKey] without having to keep track of offsets. 102 val startIndex = 103 if (params.key == null) 0 104 else DATA.indexOfFirst({ item -> item.pickerId == params.key?.pickerId ?: 1 }) 105 106 // The list is zero-based, and loadSize isn't; so, offset by 1 107 val endIndex = Math.min((startIndex + params.loadSize) - 1, DATA.lastIndex) 108 109 // Item at start position doesn't exist, so this isn't a valid page. 110 if (DATA.getOrNull(startIndex) == null) { 111 return LoadResult.Invalid() 112 } 113 114 val pageData = DATA.slice(startIndex..endIndex) 115 116 // Find the start of the next page and generate a Page key. 117 val nextRow = DATA.getOrNull(endIndex + 1) 118 val nextKey = 119 if (nextRow == null) null 120 else 121 MediaPageKey( 122 pickerId = nextRow.pickerId, 123 dateTakenMillis = nextRow.dateTakenMillisLong, 124 ) 125 126 // Find the start of the previous page and generate a Page key. 127 val prevPageRow = DATA.getOrNull((startIndex) - params.loadSize) 128 val prevKey = 129 if (prevPageRow == null) null 130 else 131 MediaPageKey( 132 pickerId = prevPageRow.pickerId, 133 dateTakenMillis = prevPageRow.dateTakenMillisLong, 134 ) 135 136 return LoadResult.Page(data = pageData, nextKey = nextKey, prevKey = prevKey) 137 } 138 139 override fun getRefreshKey(state: PagingState<MediaPageKey, Media>): MediaPageKey? { 140 return state.anchorPosition?.let { null } 141 } 142 } 143