• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.widget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.SparseIntArray;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.RemoteViews.ColorResources;
25 import android.widget.RemoteViews.InteractionHandler;
26 import android.widget.RemoteViews.RemoteCollectionItems;
27 
28 import com.android.internal.R;
29 
30 import java.util.stream.IntStream;
31 
32 /**
33  * List {@link Adapter} backed by a {@link RemoteCollectionItems}.
34  *
35  * @hide
36  */
37 class RemoteCollectionItemsAdapter extends BaseAdapter {
38 
39     private final int mViewTypeCount;
40 
41     private RemoteCollectionItems mItems;
42     private InteractionHandler mInteractionHandler;
43     private ColorResources mColorResources;
44 
45     private SparseIntArray mLayoutIdToViewType;
46 
RemoteCollectionItemsAdapter( @onNull RemoteCollectionItems items, @NonNull InteractionHandler interactionHandler, @NonNull ColorResources colorResources)47     RemoteCollectionItemsAdapter(
48             @NonNull RemoteCollectionItems items,
49             @NonNull InteractionHandler interactionHandler,
50             @NonNull ColorResources colorResources) {
51         // View type count can never increase after an adapter has been set on a ListView.
52         // Additionally, decreasing it could inhibit view recycling if the count were to back and
53         // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
54         // the lifetime of the adapter.
55         mViewTypeCount = items.getViewTypeCount();
56 
57         mItems = items;
58         mInteractionHandler = interactionHandler;
59         mColorResources = colorResources;
60 
61         initLayoutIdToViewType();
62     }
63 
64     /**
65      * Updates the data for the adapter, allowing recycling of views. Note that if the view type
66      * count has increased, a new adapter should be created and set on the AdapterView instead of
67      * calling this method.
68      */
setData( @onNull RemoteCollectionItems items, @NonNull InteractionHandler interactionHandler, @NonNull ColorResources colorResources)69     void setData(
70             @NonNull RemoteCollectionItems items,
71             @NonNull InteractionHandler interactionHandler,
72             @NonNull ColorResources colorResources) {
73         if (mViewTypeCount < items.getViewTypeCount()) {
74             throw new IllegalArgumentException(
75                     "RemoteCollectionItemsAdapter cannot increase view type count after creation");
76         }
77 
78         mItems = items;
79         mInteractionHandler = interactionHandler;
80         mColorResources = colorResources;
81 
82         initLayoutIdToViewType();
83 
84         notifyDataSetChanged();
85     }
86 
initLayoutIdToViewType()87     private void initLayoutIdToViewType() {
88         SparseIntArray previousLayoutIdToViewType = mLayoutIdToViewType;
89         mLayoutIdToViewType = new SparseIntArray(mViewTypeCount);
90 
91         int[] layoutIds = IntStream.range(0, mItems.getItemCount())
92                 .map(position -> mItems.getItemView(position).getLayoutId())
93                 .distinct()
94                 .toArray();
95         if (layoutIds.length > mViewTypeCount) {
96             throw new IllegalArgumentException(
97                     "Collection items uses " + layoutIds.length + " distinct layouts, which is "
98                             + "more than view type count of " + mViewTypeCount);
99         }
100 
101         // Tracks whether a layout id (by index, not value) has been assigned a view type.
102         boolean[] processedLayoutIdIndices = new boolean[layoutIds.length];
103         // Tracks whether a view type has been assigned to a layout id already.
104         boolean[] assignedViewTypes = new boolean[mViewTypeCount];
105 
106         if (previousLayoutIdToViewType != null) {
107             for (int i = 0; i < layoutIds.length; i++) {
108                 int layoutId = layoutIds[i];
109                 // Copy over any previously used view types for layout ids in the collection to keep
110                 // view types stable across data updates.
111                 int previousViewType = previousLayoutIdToViewType.get(layoutId, -1);
112                 // Skip this layout id if it wasn't assigned to a view type previously.
113                 if (previousViewType < 0) continue;
114 
115                 mLayoutIdToViewType.put(layoutId, previousViewType);
116                 processedLayoutIdIndices[i] = true;
117                 assignedViewTypes[previousViewType] = true;
118             }
119         }
120 
121         int lastViewType = -1;
122         for (int i = 0; i < layoutIds.length; i++) {
123             // If a view type has already been assigned to the layout id, skip it.
124             if (processedLayoutIdIndices[i]) continue;
125 
126             int layoutId = layoutIds[i];
127             // If no view type is assigned for the layout id, choose the next possible value that
128             // isn't already assigned to a layout id. There is guaranteed to be some value available
129             // due to the prior validation logic that count(distinct layout ids) <= viewTypeCount.
130             int viewType = IntStream.range(lastViewType + 1, layoutIds.length)
131                     .filter(type -> !assignedViewTypes[type])
132                     .findFirst()
133                     .orElseThrow(
134                             () -> new IllegalStateException(
135                                     "RemoteCollectionItems has more distinct layout ids than its "
136                                             + "view type count"));
137             mLayoutIdToViewType.put(layoutId, viewType);
138             processedLayoutIdIndices[i] = true;
139             assignedViewTypes[viewType] = true;
140             lastViewType = viewType;
141         }
142     }
143 
144     @Override
getCount()145     public int getCount() {
146         return mItems.getItemCount();
147     }
148 
149     @Override
getItem(int position)150     public RemoteViews getItem(int position) {
151         return mItems.getItemView(position);
152     }
153 
154     @Override
getItemId(int position)155     public long getItemId(int position) {
156         return mItems.getItemId(position);
157     }
158 
159     @Override
getItemViewType(int position)160     public int getItemViewType(int position) {
161         return mLayoutIdToViewType.get(mItems.getItemView(position).getLayoutId());
162     }
163 
164     @Override
getViewTypeCount()165     public int getViewTypeCount() {
166         return mViewTypeCount;
167     }
168 
169     @Override
hasStableIds()170     public boolean hasStableIds() {
171         return mItems.hasStableIds();
172     }
173 
174     @Nullable
175     @Override
getView(int position, @Nullable View convertView, @NonNull ViewGroup parent)176     public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
177         if (position >= getCount()) return null;
178 
179         RemoteViews item = mItems.getItemView(position);
180         item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
181         View reapplyView = getViewToReapply(convertView, item);
182 
183         // Reapply the RemoteViews if we can.
184         if (reapplyView != null) {
185             try {
186                 item.reapply(
187                         parent.getContext(),
188                         reapplyView,
189                         mInteractionHandler,
190                         null /* size */,
191                         mColorResources);
192                 return reapplyView;
193             } catch (RuntimeException e) {
194                 // We can't reapply for some reason, we'll fallback to an apply and inflate a
195                 // new view.
196             }
197         }
198 
199         return item.apply(
200                 parent.getContext(),
201                 parent,
202                 mInteractionHandler,
203                 null /* size */,
204                 mColorResources);
205     }
206 
207     /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
208     @Nullable
getViewToReapply(@ullable View convertView, @NonNull RemoteViews item)209     private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
210         if (convertView == null) return null;
211 
212         Object layoutIdTag = convertView.getTag(R.id.widget_frame);
213         if (!(layoutIdTag instanceof Integer)) return null;
214 
215         return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
216     }
217 }
218