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