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