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