1 /*
2  * Copyright 2019 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 androidx.paging
18 
19 import androidx.paging.PagingSource.LoadResult.Page
20 
21 /** Load access information blob, containing information from presenter. */
22 internal sealed class ViewportHint(
23     /**
24      * Number of loaded items presented before this hint. Calculated as the distance from this hint
25      * to first loaded item being presented: `anchorPosition - firstLoadedItemPosition`
26      *
27      * Zero indicates access at boundary Positive -> Within loaded range or in placeholders after
28      * presented items if greater than the size of all presented items. Negative -> placeholder
29      * access before first loaded item.
30      */
31     val presentedItemsBefore: Int,
32     /**
33      * Number of loaded items presented after this hint. Calculated as the distance from this hint
34      * to last loaded item being presented: `presenterSize - anchorPosition - placeholdersAfter - 1`
35      *
36      * Zero indicates access at boundary Positive -> Within loaded range or in placeholders before
37      * presented items if greater than the size of all presented items. Negative -> placeholder
38      * access after last loaded item.
39      */
40     val presentedItemsAfter: Int,
41     /**
42      * [hintOriginalPageOffset][TransformablePage.hintOriginalPageOffset] of the first presented
43      * [TransformablePage] when this [ViewportHint] was created.
44      */
45     val originalPageOffsetFirst: Int,
46     /**
47      * [hintOriginalPageOffset][TransformablePage.hintOriginalPageOffset] of the last presented
48      * [TransformablePage] when this [ViewportHint] was created.
49      */
50     val originalPageOffsetLast: Int,
51 ) {
equalsnull52     override fun equals(other: Any?): Boolean {
53         if (this === other) return true
54         if (other !is ViewportHint) return false
55 
56         return presentedItemsBefore == other.presentedItemsBefore &&
57             presentedItemsAfter == other.presentedItemsAfter &&
58             originalPageOffsetFirst == other.originalPageOffsetFirst &&
59             originalPageOffsetLast == other.originalPageOffsetLast
60     }
61 
62     /**
63      * @return Count of presented items between this hint, and either:
64      *     * the beginning of the list if [loadType] == PREPEND
65      *     * the end of the list if loadType == APPEND
66      */
presentedItemsBeyondAnchornull67     internal fun presentedItemsBeyondAnchor(loadType: LoadType): Int =
68         when (loadType) {
69             LoadType.REFRESH ->
70                 throw IllegalArgumentException("Cannot get presentedItems for loadType: REFRESH")
71             LoadType.PREPEND -> presentedItemsBefore
72             LoadType.APPEND -> presentedItemsAfter
73         }
74 
hashCodenull75     override fun hashCode(): Int {
76         return presentedItemsBefore.hashCode() +
77             presentedItemsAfter.hashCode() +
78             originalPageOffsetFirst.hashCode() +
79             originalPageOffsetLast.hashCode()
80     }
81 
82     /**
83      * [ViewportHint] reporting presenter state after receiving initial page. An [Initial] hint
84      * should never take precedence over an [Access] hint and is only used to inform [PageFetcher]
85      * how many items from the initial page load were presented by [PagingDataPresenter]
86      */
87     class Initial(
88         presentedItemsBefore: Int,
89         presentedItemsAfter: Int,
90         originalPageOffsetFirst: Int,
91         originalPageOffsetLast: Int
92     ) :
93         ViewportHint(
94             presentedItemsBefore = presentedItemsBefore,
95             presentedItemsAfter = presentedItemsAfter,
96             originalPageOffsetFirst = originalPageOffsetFirst,
97             originalPageOffsetLast = originalPageOffsetLast,
98         ) {
toStringnull99         override fun toString(): String {
100             return """ViewportHint.Initial(
101             |    presentedItemsBefore=$presentedItemsBefore,
102             |    presentedItemsAfter=$presentedItemsAfter,
103             |    originalPageOffsetFirst=$originalPageOffsetFirst,
104             |    originalPageOffsetLast=$originalPageOffsetLast,
105             |)"""
106                 .trimMargin()
107         }
108     }
109 
110     /**
111      * [ViewportHint] representing an item access that should be used to trigger loads to fulfill
112      * prefetch distance.
113      */
114     class Access(
115         /** Page index offset from initial load */
116         val pageOffset: Int,
117         /**
118          * Original index of item in the [Page] with [pageOffset].
119          *
120          * Three cases to consider:
121          * - [indexInPage] in Page.data.indices -> Hint references original item directly
122          * - [indexInPage] > Page.data.indices -> Hint references a placeholder after the last
123          *   presented item.
124          * - [indexInPage] < 0 -> Hint references a placeholder before the first presented item.
125          */
126         val indexInPage: Int,
127         presentedItemsBefore: Int,
128         presentedItemsAfter: Int,
129         originalPageOffsetFirst: Int,
130         originalPageOffsetLast: Int
131     ) :
132         ViewportHint(
133             presentedItemsBefore = presentedItemsBefore,
134             presentedItemsAfter = presentedItemsAfter,
135             originalPageOffsetFirst = originalPageOffsetFirst,
136             originalPageOffsetLast = originalPageOffsetLast,
137         ) {
equalsnull138         override fun equals(other: Any?): Boolean {
139             if (this === other) return true
140             if (other !is Access) return false
141 
142             return pageOffset == other.pageOffset &&
143                 indexInPage == other.indexInPage &&
144                 presentedItemsBefore == other.presentedItemsBefore &&
145                 presentedItemsAfter == other.presentedItemsAfter &&
146                 originalPageOffsetFirst == other.originalPageOffsetFirst &&
147                 originalPageOffsetLast == other.originalPageOffsetLast
148         }
149 
hashCodenull150         override fun hashCode(): Int {
151             return super.hashCode() + pageOffset.hashCode() + indexInPage.hashCode()
152         }
153 
toStringnull154         override fun toString(): String {
155             return """ViewportHint.Access(
156             |    pageOffset=$pageOffset,
157             |    indexInPage=$indexInPage,
158             |    presentedItemsBefore=$presentedItemsBefore,
159             |    presentedItemsAfter=$presentedItemsAfter,
160             |    originalPageOffsetFirst=$originalPageOffsetFirst,
161             |    originalPageOffsetLast=$originalPageOffsetLast,
162             |)"""
163                 .trimMargin()
164         }
165     }
166 }
167