Loading core/api/current.txt +20 −0 Original line number Diff line number Diff line Loading @@ -55030,6 +55030,7 @@ package android.widget { method public void setRelativeScrollPosition(@IdRes int, int); method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems); method public void setScrollPosition(@IdRes int, int); method public void setShort(@IdRes int, String, short); method public void setString(@IdRes int, String, String); Loading Loading @@ -55069,6 +55070,25 @@ package android.widget { ctor public RemoteViews.ActionException(String); } public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable { method public int describeContents(); method public int getItemCount(); method public long getItemId(int); method @NonNull public android.widget.RemoteViews getItemView(int); method public int getViewTypeCount(); method public boolean hasStableIds(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews.RemoteCollectionItems> CREATOR; } public static final class RemoteViews.RemoteCollectionItems.Builder { ctor public RemoteViews.RemoteCollectionItems.Builder(); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder addItem(long, @NonNull android.widget.RemoteViews); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems build(); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setHasStableIds(boolean); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setViewTypeCount(int); } public static class RemoteViews.RemoteResponse { ctor public RemoteViews.RemoteResponse(); method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String); core/java/android/widget/RemoteCollectionItemsAdapter.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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; } } core/java/android/widget/RemoteViews.java +288 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.Px; import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; Loading Loading @@ -74,6 +75,7 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.LongArray; import android.util.Pair; import android.util.SizeF; import android.util.SparseIntArray; Loading Loading @@ -112,6 +114,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; Loading Loading @@ -224,6 +227,7 @@ public class RemoteViews implements Parcelable, Filter { private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28; private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29; private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30; private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31; /** @hide **/ @IntDef(prefix = "MARGIN_", value = { Loading Loading @@ -899,6 +903,72 @@ public class RemoteViews implements Parcelable, Filter { ArrayList<RemoteViews> list; } private static class SetRemoteCollectionItemListAdapterAction extends Action { private final RemoteCollectionItems mItems; SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) { viewId = id; mItems = items; } SetRemoteCollectionItemListAdapterAction(Parcel parcel) { viewId = parcel.readInt(); mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeTypedObject(mItems, flags); } @Override public void apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources) throws ActionException { View target = root.findViewById(viewId); if (target == null) return; if (!(target instanceof AdapterView)) { Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not " + "an AdapterView (id: " + viewId + ")"); return; } AdapterView adapterView = (AdapterView) target; Adapter adapter = adapterView.getAdapter(); // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type // count hasn't increased. Note that AbsListView allocates a fixed size array for view // recycling in setAdapter, so we must call setAdapter again if the number of view types // increases. if (adapter instanceof RemoteCollectionItemsAdapter && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) { try { ((RemoteCollectionItemsAdapter) adapter).setData( mItems, handler, colorResources); } catch (Throwable throwable) { // setData should never failed with the validation in the items builder, but if // it does, catch and rethrow. throw new ActionException(throwable); } return; } try { adapterView.setAdapter( new RemoteCollectionItemsAdapter(mItems, handler, colorResources)); } catch (Throwable throwable) { // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to // a type error. throw new ActionException(throwable); } } @Override public int getActionTag() { return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG; } } private class SetRemoteViewsAdapterIntent extends Action { public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) { this.viewId = id; Loading Loading @@ -3543,6 +3613,8 @@ public class RemoteViews implements Parcelable, Filter { return new SetOnCheckedChangeResponse(parcel); case NIGHT_MODE_REFLECTION_ACTION_TAG: return new NightModeReflectionAction(parcel); case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG: return new SetRemoteCollectionItemListAdapterAction(parcel); default: throw new ActionException("Tag " + tag + " not found"); } Loading Loading @@ -4214,6 +4286,25 @@ public class RemoteViews implements Parcelable, Filter { addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); } /** * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. * This is a simpler but less flexible approach to populating collection widgets. Its use is * encouraged for most scenarios, as long as the total memory within the list of RemoteViews * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. * * This API is supported in the compatibility library for previous API levels, see * RemoteViewsCompat. * * @param viewId The id of the {@link AdapterView}. * @param items The items to display in the {@link AdapterView}. */ public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) { addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items)); } /** * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. * Loading Loading @@ -6026,6 +6117,203 @@ public class RemoteViews implements Parcelable, Filter { return true; } /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */ public static final class RemoteCollectionItems implements Parcelable { private final long[] mIds; private final RemoteViews[] mViews; private final boolean mHasStableIds; private final int mViewTypeCount; RemoteCollectionItems( long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) { mIds = ids; mViews = views; mHasStableIds = hasStableIds; mViewTypeCount = viewTypeCount; if (ids.length != views.length) { throw new IllegalArgumentException( "RemoteCollectionItems has different number of ids and views"); } if (viewTypeCount < 1) { throw new IllegalArgumentException("View type count must be >= 1"); } int layoutIdCount = (int) Arrays.stream(views) .mapToInt(RemoteViews::getLayoutId) .distinct() .count(); if (layoutIdCount > viewTypeCount) { throw new IllegalArgumentException( "View type count is set to " + viewTypeCount + ", but the collection " + "contains " + layoutIdCount + " different layout ids"); } } RemoteCollectionItems(Parcel in) { int length = in.readInt(); mIds = new long[length]; in.readLongArray(mIds); mViews = new RemoteViews[length]; in.readTypedArray(mViews, RemoteViews.CREATOR); mHasStableIds = in.readBoolean(); mViewTypeCount = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mIds.length); dest.writeLongArray(mIds); dest.writeTypedArray(mViews, flags); dest.writeBoolean(mHasStableIds); dest.writeInt(mViewTypeCount); } /** * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id * should be considered meaningful across collection updates. * * @return Id for the position. */ public long getItemId(int position) { return mIds[position]; } /** * Returns the {@link RemoteViews} to display at {@code position}. * * @return RemoteViews for the position. */ @NonNull public RemoteViews getItemView(int position) { return mViews[position]; } /** * Returns the number of elements in the collection. * * @return Count of items. */ public int getItemCount() { return mIds.length; } /** * Returns the view type count for the collection when used in an adapter * * @return Count of view types for the collection when used in an adapter. * @see android.widget.Adapter#getViewTypeCount() */ public int getViewTypeCount() { return mViewTypeCount; } /** * Indicates whether the item ids are stable across changes to the underlying data. * * @return True if the same id always refers to the same object. * @see android.widget.Adapter#hasStableIds() */ public boolean hasStableIds() { return mHasStableIds; } @NonNull public static final Creator<RemoteCollectionItems> CREATOR = new Creator<RemoteCollectionItems>() { @NonNull @Override public RemoteCollectionItems createFromParcel(@NonNull Parcel source) { return new RemoteCollectionItems(source); } @NonNull @Override public RemoteCollectionItems[] newArray(int size) { return new RemoteCollectionItems[size]; } }; /** Builder class for {@link RemoteCollectionItems} objects.*/ public static final class Builder { private final LongArray mIds = new LongArray(); private final List<RemoteViews> mViews = new ArrayList<>(); private boolean mHasStableIds; private int mViewTypeCount; /** * Adds a {@link RemoteViews} to the collection. * * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to * indicate that ids are stable across changes to the collection. * @param view RemoteViews to display for the row. */ @NonNull // Covered by getItemId, getItemView, getItemCount. @SuppressLint("MissingGetterMatchingBuilder") public Builder addItem(long id, @NonNull RemoteViews view) { if (view == null) throw new NullPointerException(); if (view.hasMultipleLayouts()) { throw new IllegalArgumentException( "RemoteViews used in a RemoteCollectionItems cannot specify separate " + "layouts for orientations or sizes."); } mIds.add(id); mViews.add(view); return this; } /** * Sets whether the item ids are stable across changes to the underlying data. * * @see android.widget.Adapter#hasStableIds() */ @NonNull public Builder setHasStableIds(boolean hasStableIds) { mHasStableIds = hasStableIds; return this; } /** * Sets the view type count for the collection when used in an adapter. This can be set * to the maximum number of different layout ids that will be used by RemoteViews in * this collection. * * If this value is not set, then a value will be inferred from the provided items. As * a result, the adapter may need to be recreated when the list is updated with * previously unseen RemoteViews layouts for new items. * * @see android.widget.Adapter#getViewTypeCount() */ @NonNull public Builder setViewTypeCount(int viewTypeCount) { mViewTypeCount = viewTypeCount; return this; } /** Creates the {@link RemoteCollectionItems} defined by this builder. */ @NonNull public RemoteCollectionItems build() { if (mViewTypeCount < 1) { // If a view type count wasn't specified, set it to be the number of distinct // layout ids used in the items. mViewTypeCount = (int) mViews.stream() .mapToInt(RemoteViews::getLayoutId) .distinct() .count(); } return new RemoteCollectionItems( mIds.toArray(), mViews.toArray(new RemoteViews[0]), mHasStableIds, Math.max(mViewTypeCount, 1)); } } } /** * Set the ID of the top-level view of the XML layout. * Loading Loading
core/api/current.txt +20 −0 Original line number Diff line number Diff line Loading @@ -55030,6 +55030,7 @@ package android.widget { method public void setRelativeScrollPosition(@IdRes int, int); method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems); method public void setScrollPosition(@IdRes int, int); method public void setShort(@IdRes int, String, short); method public void setString(@IdRes int, String, String); Loading Loading @@ -55069,6 +55070,25 @@ package android.widget { ctor public RemoteViews.ActionException(String); } public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable { method public int describeContents(); method public int getItemCount(); method public long getItemId(int); method @NonNull public android.widget.RemoteViews getItemView(int); method public int getViewTypeCount(); method public boolean hasStableIds(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews.RemoteCollectionItems> CREATOR; } public static final class RemoteViews.RemoteCollectionItems.Builder { ctor public RemoteViews.RemoteCollectionItems.Builder(); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder addItem(long, @NonNull android.widget.RemoteViews); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems build(); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setHasStableIds(boolean); method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setViewTypeCount(int); } public static class RemoteViews.RemoteResponse { ctor public RemoteViews.RemoteResponse(); method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String);
core/java/android/widget/RemoteCollectionItemsAdapter.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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; } }
core/java/android/widget/RemoteViews.java +288 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.Px; import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; Loading Loading @@ -74,6 +75,7 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.LongArray; import android.util.Pair; import android.util.SizeF; import android.util.SparseIntArray; Loading Loading @@ -112,6 +114,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; Loading Loading @@ -224,6 +227,7 @@ public class RemoteViews implements Parcelable, Filter { private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28; private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29; private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30; private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31; /** @hide **/ @IntDef(prefix = "MARGIN_", value = { Loading Loading @@ -899,6 +903,72 @@ public class RemoteViews implements Parcelable, Filter { ArrayList<RemoteViews> list; } private static class SetRemoteCollectionItemListAdapterAction extends Action { private final RemoteCollectionItems mItems; SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) { viewId = id; mItems = items; } SetRemoteCollectionItemListAdapterAction(Parcel parcel) { viewId = parcel.readInt(); mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeTypedObject(mItems, flags); } @Override public void apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources) throws ActionException { View target = root.findViewById(viewId); if (target == null) return; if (!(target instanceof AdapterView)) { Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not " + "an AdapterView (id: " + viewId + ")"); return; } AdapterView adapterView = (AdapterView) target; Adapter adapter = adapterView.getAdapter(); // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type // count hasn't increased. Note that AbsListView allocates a fixed size array for view // recycling in setAdapter, so we must call setAdapter again if the number of view types // increases. if (adapter instanceof RemoteCollectionItemsAdapter && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) { try { ((RemoteCollectionItemsAdapter) adapter).setData( mItems, handler, colorResources); } catch (Throwable throwable) { // setData should never failed with the validation in the items builder, but if // it does, catch and rethrow. throw new ActionException(throwable); } return; } try { adapterView.setAdapter( new RemoteCollectionItemsAdapter(mItems, handler, colorResources)); } catch (Throwable throwable) { // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to // a type error. throw new ActionException(throwable); } } @Override public int getActionTag() { return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG; } } private class SetRemoteViewsAdapterIntent extends Action { public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) { this.viewId = id; Loading Loading @@ -3543,6 +3613,8 @@ public class RemoteViews implements Parcelable, Filter { return new SetOnCheckedChangeResponse(parcel); case NIGHT_MODE_REFLECTION_ACTION_TAG: return new NightModeReflectionAction(parcel); case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG: return new SetRemoteCollectionItemListAdapterAction(parcel); default: throw new ActionException("Tag " + tag + " not found"); } Loading Loading @@ -4214,6 +4286,25 @@ public class RemoteViews implements Parcelable, Filter { addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); } /** * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. * This is a simpler but less flexible approach to populating collection widgets. Its use is * encouraged for most scenarios, as long as the total memory within the list of RemoteViews * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. * * This API is supported in the compatibility library for previous API levels, see * RemoteViewsCompat. * * @param viewId The id of the {@link AdapterView}. * @param items The items to display in the {@link AdapterView}. */ public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) { addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items)); } /** * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. * Loading Loading @@ -6026,6 +6117,203 @@ public class RemoteViews implements Parcelable, Filter { return true; } /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */ public static final class RemoteCollectionItems implements Parcelable { private final long[] mIds; private final RemoteViews[] mViews; private final boolean mHasStableIds; private final int mViewTypeCount; RemoteCollectionItems( long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) { mIds = ids; mViews = views; mHasStableIds = hasStableIds; mViewTypeCount = viewTypeCount; if (ids.length != views.length) { throw new IllegalArgumentException( "RemoteCollectionItems has different number of ids and views"); } if (viewTypeCount < 1) { throw new IllegalArgumentException("View type count must be >= 1"); } int layoutIdCount = (int) Arrays.stream(views) .mapToInt(RemoteViews::getLayoutId) .distinct() .count(); if (layoutIdCount > viewTypeCount) { throw new IllegalArgumentException( "View type count is set to " + viewTypeCount + ", but the collection " + "contains " + layoutIdCount + " different layout ids"); } } RemoteCollectionItems(Parcel in) { int length = in.readInt(); mIds = new long[length]; in.readLongArray(mIds); mViews = new RemoteViews[length]; in.readTypedArray(mViews, RemoteViews.CREATOR); mHasStableIds = in.readBoolean(); mViewTypeCount = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mIds.length); dest.writeLongArray(mIds); dest.writeTypedArray(mViews, flags); dest.writeBoolean(mHasStableIds); dest.writeInt(mViewTypeCount); } /** * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id * should be considered meaningful across collection updates. * * @return Id for the position. */ public long getItemId(int position) { return mIds[position]; } /** * Returns the {@link RemoteViews} to display at {@code position}. * * @return RemoteViews for the position. */ @NonNull public RemoteViews getItemView(int position) { return mViews[position]; } /** * Returns the number of elements in the collection. * * @return Count of items. */ public int getItemCount() { return mIds.length; } /** * Returns the view type count for the collection when used in an adapter * * @return Count of view types for the collection when used in an adapter. * @see android.widget.Adapter#getViewTypeCount() */ public int getViewTypeCount() { return mViewTypeCount; } /** * Indicates whether the item ids are stable across changes to the underlying data. * * @return True if the same id always refers to the same object. * @see android.widget.Adapter#hasStableIds() */ public boolean hasStableIds() { return mHasStableIds; } @NonNull public static final Creator<RemoteCollectionItems> CREATOR = new Creator<RemoteCollectionItems>() { @NonNull @Override public RemoteCollectionItems createFromParcel(@NonNull Parcel source) { return new RemoteCollectionItems(source); } @NonNull @Override public RemoteCollectionItems[] newArray(int size) { return new RemoteCollectionItems[size]; } }; /** Builder class for {@link RemoteCollectionItems} objects.*/ public static final class Builder { private final LongArray mIds = new LongArray(); private final List<RemoteViews> mViews = new ArrayList<>(); private boolean mHasStableIds; private int mViewTypeCount; /** * Adds a {@link RemoteViews} to the collection. * * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to * indicate that ids are stable across changes to the collection. * @param view RemoteViews to display for the row. */ @NonNull // Covered by getItemId, getItemView, getItemCount. @SuppressLint("MissingGetterMatchingBuilder") public Builder addItem(long id, @NonNull RemoteViews view) { if (view == null) throw new NullPointerException(); if (view.hasMultipleLayouts()) { throw new IllegalArgumentException( "RemoteViews used in a RemoteCollectionItems cannot specify separate " + "layouts for orientations or sizes."); } mIds.add(id); mViews.add(view); return this; } /** * Sets whether the item ids are stable across changes to the underlying data. * * @see android.widget.Adapter#hasStableIds() */ @NonNull public Builder setHasStableIds(boolean hasStableIds) { mHasStableIds = hasStableIds; return this; } /** * Sets the view type count for the collection when used in an adapter. This can be set * to the maximum number of different layout ids that will be used by RemoteViews in * this collection. * * If this value is not set, then a value will be inferred from the provided items. As * a result, the adapter may need to be recreated when the list is updated with * previously unseen RemoteViews layouts for new items. * * @see android.widget.Adapter#getViewTypeCount() */ @NonNull public Builder setViewTypeCount(int viewTypeCount) { mViewTypeCount = viewTypeCount; return this; } /** Creates the {@link RemoteCollectionItems} defined by this builder. */ @NonNull public RemoteCollectionItems build() { if (mViewTypeCount < 1) { // If a view type count wasn't specified, set it to be the number of distinct // layout ids used in the items. mViewTypeCount = (int) mViews.stream() .mapToInt(RemoteViews::getLayoutId) .distinct() .count(); } return new RemoteCollectionItems( mIds.toArray(), mViews.toArray(new RemoteViews[0]), mHasStableIds, Math.max(mViewTypeCount, 1)); } } } /** * Set the ID of the top-level view of the XML layout. * Loading