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