1 /*
2  * Copyright 2023 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.glance.appwidget
18 
19 import android.annotation.SuppressLint
20 import android.widget.RemoteViews
21 
22 /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */
23 internal class RemoteCollectionItems
24 private constructor(
25     private val ids: LongArray,
26     private val views: Array<RemoteViews>,
27     private val hasStableIds: Boolean,
28     private val _viewTypeCount: Int
29 ) {
30     init {
<lambda>null31         require(ids.size == views.size) {
32             "RemoteCollectionItems has different number of ids and views"
33         }
<lambda>null34         require(_viewTypeCount >= 1) { "View type count must be >= 1" }
<lambda>null35         val layoutIdCount = views.map { it.layoutId }.distinct().count()
<lambda>null36         require(layoutIdCount <= _viewTypeCount) {
37             "View type count is set to $_viewTypeCount, but the collection contains " +
38                 "$layoutIdCount different layout ids"
39         }
40     }
41 
42     /**
43      * Returns the id for [position]. See [hasStableIds] for whether this id should be considered
44      * meaningful across collection updates.
45      *
46      * @return Id for the position.
47      */
getItemIdnull48     fun getItemId(position: Int): Long = ids[position]
49 
50     /**
51      * Returns the [RemoteViews] to display at [position].
52      *
53      * @return RemoteViews for the position.
54      */
55     fun getItemView(position: Int): RemoteViews = views[position]
56 
57     /**
58      * Returns the number of elements in the collection.
59      *
60      * @return Count of items.
61      */
62     val itemCount: Int
63         get() = ids.size
64 
65     /**
66      * Returns the view type count for the collection when used in an adapter
67      *
68      * @return Count of view types for the collection when used in an adapter.
69      * @see android.widget.Adapter.getViewTypeCount
70      */
71     val viewTypeCount: Int
72         get() = _viewTypeCount
73 
74     /**
75      * Indicates whether the item ids are stable across changes to the underlying data.
76      *
77      * @return True if the same id always refers to the same object.
78      * @see android.widget.Adapter.hasStableIds
79      */
80     fun hasStableIds(): Boolean = hasStableIds
81 
82     /** Builder class for [RemoteCollectionItems] objects. */
83     class Builder {
84         private val ids = arrayListOf<Long>()
85         private val views = arrayListOf<RemoteViews>()
86         private var hasStableIds = false
87         private var viewTypeCount = 0
88 
89         /**
90          * Adds a [RemoteViews] to the collection.
91          *
92          * @param id Id to associate with the row. Use [.setHasStableIds] to indicate that ids are
93          *   stable across changes to the collection.
94          * @param view RemoteViews to display for the row.
95          */
96         // Covered by getItemId, getItemView, getItemCount.
97         @SuppressLint("MissingGetterMatchingBuilder")
98         fun addItem(id: Long, view: RemoteViews): Builder {
99             ids.add(id)
100             views.add(view)
101             return this
102         }
103 
104         /**
105          * Sets whether the item ids are stable across changes to the underlying data.
106          *
107          * @see android.widget.Adapter.hasStableIds
108          */
109         fun setHasStableIds(hasStableIds: Boolean): Builder {
110             this.hasStableIds = hasStableIds
111             return this
112         }
113 
114         /**
115          * Sets the view type count for the collection when used in an adapter. This can be set to
116          * the maximum number of different layout ids that will be used by RemoteViews in this
117          * collection.
118          *
119          * If this value is not set, then a value will be inferred from the provided items. As a
120          * result, the adapter may need to be recreated when the list is updated with previously
121          * unseen RemoteViews layouts for new items.
122          *
123          * @see android.widget.Adapter.getViewTypeCount
124          */
125         fun setViewTypeCount(viewTypeCount: Int): Builder {
126             this.viewTypeCount = viewTypeCount
127             return this
128         }
129 
130         /** Creates the [RemoteCollectionItems] defined by this builder. */
131         fun build(): RemoteCollectionItems {
132             if (viewTypeCount < 1) {
133                 // If a view type count wasn't specified, set it to be the number of distinct
134                 // layout ids used in the items.
135                 viewTypeCount = views.map { it.layoutId }.distinct().count()
136             }
137             return RemoteCollectionItems(
138                 ids.toLongArray(),
139                 views.toTypedArray(),
140                 hasStableIds,
141                 maxOf(viewTypeCount, 1)
142             )
143         }
144     }
145 
146     companion object {
147         val Empty =
148             RemoteCollectionItems(
149                 ids = longArrayOf(),
150                 views = emptyArray(),
151                 hasStableIds = false,
152                 _viewTypeCount = 1
153             )
154     }
155 }
156