summaryrefslogtreecommitdiff
path: root/core/java/android/widget/RemoteCollectionItemsAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget/RemoteCollectionItemsAdapter.java')
-rw-r--r--core/java/android/widget/RemoteCollectionItemsAdapter.java217
1 files changed, 217 insertions, 0 deletions
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
new file mode 100644
index 000000000000..d84330828bf7
--- /dev/null
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.ColorResources;
+import android.widget.RemoteViews.InteractionHandler;
+import android.widget.RemoteViews.RemoteCollectionItems;
+
+import com.android.internal.R;
+
+import java.util.stream.IntStream;
+
+/**
+ * List {@link Adapter} backed by a {@link RemoteCollectionItems}.
+ *
+ * @hide
+ */
+class RemoteCollectionItemsAdapter extends BaseAdapter {
+
+ private final int mViewTypeCount;
+
+ private RemoteCollectionItems mItems;
+ private InteractionHandler mInteractionHandler;
+ private ColorResources mColorResources;
+
+ private SparseIntArray mLayoutIdToViewType;
+
+ RemoteCollectionItemsAdapter(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ // View type count can never increase after an adapter has been set on a ListView.
+ // Additionally, decreasing it could inhibit view recycling if the count were to back and
+ // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
+ // the lifetime of the adapter.
+ mViewTypeCount = items.getViewTypeCount();
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+ }
+
+ /**
+ * Updates the data for the adapter, allowing recycling of views. Note that if the view type
+ * count has increased, a new adapter should be created and set on the AdapterView instead of
+ * calling this method.
+ */
+ void setData(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ if (mViewTypeCount < items.getViewTypeCount()) {
+ throw new IllegalArgumentException(
+ "RemoteCollectionItemsAdapter cannot increase view type count after creation");
+ }
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+
+ notifyDataSetChanged();
+ }
+
+ private void initLayoutIdToViewType() {
+ SparseIntArray previousLayoutIdToViewType = mLayoutIdToViewType;
+ mLayoutIdToViewType = new SparseIntArray(mViewTypeCount);
+
+ int[] layoutIds = IntStream.range(0, mItems.getItemCount())
+ .map(position -> mItems.getItemView(position).getLayoutId())
+ .distinct()
+ .toArray();
+ if (layoutIds.length > mViewTypeCount) {
+ throw new IllegalArgumentException(
+ "Collection items uses " + layoutIds.length + " distinct layouts, which is "
+ + "more than view type count of " + mViewTypeCount);
+ }
+
+ // Tracks whether a layout id (by index, not value) has been assigned a view type.
+ boolean[] processedLayoutIdIndices = new boolean[layoutIds.length];
+ // Tracks whether a view type has been assigned to a layout id already.
+ boolean[] assignedViewTypes = new boolean[mViewTypeCount];
+
+ if (previousLayoutIdToViewType != null) {
+ for (int i = 0; i < layoutIds.length; i++) {
+ int layoutId = layoutIds[i];
+ // Copy over any previously used view types for layout ids in the collection to keep
+ // view types stable across data updates.
+ int previousViewType = previousLayoutIdToViewType.get(layoutId, -1);
+ // Skip this layout id if it wasn't assigned to a view type previously.
+ if (previousViewType < 0) continue;
+
+ mLayoutIdToViewType.put(layoutId, previousViewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[previousViewType] = true;
+ }
+ }
+
+ int lastViewType = -1;
+ for (int i = 0; i < layoutIds.length; i++) {
+ // If a view type has already been assigned to the layout id, skip it.
+ if (processedLayoutIdIndices[i]) continue;
+
+ int layoutId = layoutIds[i];
+ // If no view type is assigned for the layout id, choose the next possible value that
+ // isn't already assigned to a layout id. There is guaranteed to be some value available
+ // due to the prior validation logic that count(distinct layout ids) <= viewTypeCount.
+ int viewType = IntStream.range(lastViewType + 1, layoutIds.length)
+ .filter(type -> !assignedViewTypes[type])
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalStateException(
+ "RemoteCollectionItems has more distinct layout ids than its "
+ + "view type count"));
+ mLayoutIdToViewType.put(layoutId, viewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[viewType] = true;
+ lastViewType = viewType;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.getItemCount();
+ }
+
+ @Override
+ public RemoteViews getItem(int position) {
+ return mItems.getItemView(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.getItemId(position);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mLayoutIdToViewType.get(mItems.getItemView(position).getLayoutId());
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return mItems.hasStableIds();
+ }
+
+ @Nullable
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (position >= getCount()) return null;
+
+ RemoteViews item = mItems.getItemView(position);
+ item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+ View reapplyView = getViewToReapply(convertView, item);
+
+ // Reapply the RemoteViews if we can.
+ if (reapplyView != null) {
+ try {
+ item.reapply(
+ parent.getContext(),
+ reapplyView,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ return reapplyView;
+ } catch (RuntimeException e) {
+ // We can't reapply for some reason, we'll fallback to an apply and inflate a
+ // new view.
+ }
+ }
+
+ return item.apply(
+ parent.getContext(),
+ parent,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ }
+
+ /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
+ @Nullable
+ private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
+ if (convertView == null) return null;
+
+ Object layoutIdTag = convertView.getTag(R.id.widget_frame);
+ if (!(layoutIdTag instanceof Integer)) return null;
+
+ return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
+ }
+}