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