Add API 31 sources Test: None Change-Id: Ie45894f7a232b2a15e2439b2527ca1813f334cc5
diff --git a/android/widget/ArrayAdapter.java b/android/widget/ArrayAdapter.java new file mode 100644 index 0000000..de9f76d --- /dev/null +++ b/android/widget/ArrayAdapter.java
@@ -0,0 +1,624 @@ +/* + * Copyright (C) 2006 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.ArrayRes; +import android.annotation.IdRes; +import android.annotation.LayoutRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * You can use this adapter to provide views for an {@link AdapterView}, + * Returns a view for each object in a collection of data objects you + * provide, and can be used with list-based user interface widgets such as + * {@link ListView} or {@link Spinner}. + * <p> + * By default, the array adapter creates a view by calling {@link Object#toString()} on each + * data object in the collection you provide, and places the result in a TextView. + * You may also customize what type of view is used for the data object in the collection. + * To customize what type of view is used for the data object, + * override {@link #getView(int, View, ViewGroup)} + * and inflate a view resource. + * </p> + * <p> + * For an example of using an array adapter with a ListView, see the + * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#AdapterViews"> + * Adapter Views</a> guide. + * </p> + * <p> + * For an example of using an array adapter with a Spinner, see the + * <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide. + * </p> + * <p class="note"><strong>Note:</strong> + * If you are considering using array adapter with a ListView, consider using + * {@link android.support.v7.widget.RecyclerView} instead. + * RecyclerView offers similar features with better performance and more flexibility than + * ListView provides. + * See the + * <a href="https://developer.android.com/guide/topics/ui/layout/recyclerview.html"> + * Recycler View</a> guide.</p> + */ +public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter { + /** + * Lock used to modify the content of {@link #mObjects}. Any write operation + * performed on the array should be synchronized on this lock. This lock is also + * used by the filter (see {@link #getFilter()} to make a synchronized copy of + * the original array of data. + */ + @UnsupportedAppUsage + private final Object mLock = new Object(); + + private final LayoutInflater mInflater; + + private final Context mContext; + + /** + * The resource indicating what views to inflate to display the content of this + * array adapter. + */ + private final int mResource; + + /** + * The resource indicating what views to inflate to display the content of this + * array adapter in a drop down widget. + */ + private int mDropDownResource; + + /** + * Contains the list of objects that represent the data of this ArrayAdapter. + * The content of this list is referred to as "the array" in the documentation. + */ + @UnsupportedAppUsage + private List<T> mObjects; + + /** + * Indicates whether the contents of {@link #mObjects} came from static resources. + */ + private boolean mObjectsFromResources; + + /** + * If the inflated resource is not a TextView, {@code mFieldId} is used to find + * a TextView inside the inflated views hierarchy. This field must contain the + * identifier that matches the one defined in the resource file. + */ + private int mFieldId = 0; + + /** + * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever + * {@link #mObjects} is modified. + */ + private boolean mNotifyOnChange = true; + + // A copy of the original mObjects array, initialized from and then used instead as soon as + // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values. + @UnsupportedAppUsage + private ArrayList<T> mOriginalValues; + private ArrayFilter mFilter; + + /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */ + private LayoutInflater mDropDownInflater; + + /** + * Constructor + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a TextView to use when + * instantiating views. + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) { + this(context, resource, 0, new ArrayList<>()); + } + + /** + * Constructor + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a layout to use when + * instantiating views. + * @param textViewResourceId The id of the TextView within the layout resource to be populated + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId) { + this(context, resource, textViewResourceId, new ArrayList<>()); + } + + /** + * Constructor. This constructor will result in the underlying data collection being + * immutable, so methods such as {@link #clear()} will throw an exception. + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a TextView to use when + * instantiating views. + * @param objects The objects to represent in the ListView. + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) { + this(context, resource, 0, Arrays.asList(objects)); + } + + /** + * Constructor. This constructor will result in the underlying data collection being + * immutable, so methods such as {@link #clear()} will throw an exception. + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a layout to use when + * instantiating views. + * @param textViewResourceId The id of the TextView within the layout resource to be populated + * @param objects The objects to represent in the ListView. + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId, @NonNull T[] objects) { + this(context, resource, textViewResourceId, Arrays.asList(objects)); + } + + /** + * Constructor + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a TextView to use when + * instantiating views. + * @param objects The objects to represent in the ListView. + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @NonNull List<T> objects) { + this(context, resource, 0, objects); + } + + /** + * Constructor + * + * @param context The current context. + * @param resource The resource ID for a layout file containing a layout to use when + * instantiating views. + * @param textViewResourceId The id of the TextView within the layout resource to be populated + * @param objects The objects to represent in the ListView. + */ + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId, @NonNull List<T> objects) { + this(context, resource, textViewResourceId, objects, false); + } + + private ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) { + mContext = context; + mInflater = LayoutInflater.from(context); + mResource = mDropDownResource = resource; + mObjects = objects; + mObjectsFromResources = objsFromResources; + mFieldId = textViewResourceId; + } + + /** + * Adds the specified object at the end of the array. + * + * @param object The object to add at the end of the array. + * @throws UnsupportedOperationException if the underlying data collection is immutable + */ + public void add(@Nullable T object) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.add(object); + } else { + mObjects.add(object); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Adds the specified Collection at the end of the array. + * + * @param collection The Collection to add at the end of the array. + * @throws UnsupportedOperationException if the <tt>addAll</tt> operation + * is not supported by this list + * @throws ClassCastException if the class of an element of the specified + * collection prevents it from being added to this list + * @throws NullPointerException if the specified collection contains one + * or more null elements and this list does not permit null + * elements, or if the specified collection is null + * @throws IllegalArgumentException if some property of an element of the + * specified collection prevents it from being added to this list + */ + public void addAll(@NonNull Collection<? extends T> collection) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.addAll(collection); + } else { + mObjects.addAll(collection); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Adds the specified items at the end of the array. + * + * @param items The items to add at the end of the array. + * @throws UnsupportedOperationException if the underlying data collection is immutable + */ + public void addAll(T ... items) { + synchronized (mLock) { + if (mOriginalValues != null) { + Collections.addAll(mOriginalValues, items); + } else { + Collections.addAll(mObjects, items); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Inserts the specified object at the specified index in the array. + * + * @param object The object to insert into the array. + * @param index The index at which the object must be inserted. + * @throws UnsupportedOperationException if the underlying data collection is immutable + */ + public void insert(@Nullable T object, int index) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.add(index, object); + } else { + mObjects.add(index, object); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Removes the specified object from the array. + * + * @param object The object to remove. + * @throws UnsupportedOperationException if the underlying data collection is immutable + */ + public void remove(@Nullable T object) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.remove(object); + } else { + mObjects.remove(object); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Remove all elements from the list. + * + * @throws UnsupportedOperationException if the underlying data collection is immutable + */ + public void clear() { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.clear(); + } else { + mObjects.clear(); + } + mObjectsFromResources = false; + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + /** + * Sorts the content of this adapter using the specified comparator. + * + * @param comparator The comparator used to sort the objects contained + * in this adapter. + */ + public void sort(@NonNull Comparator<? super T> comparator) { + synchronized (mLock) { + if (mOriginalValues != null) { + Collections.sort(mOriginalValues, comparator); + } else { + Collections.sort(mObjects, comparator); + } + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + mNotifyOnChange = true; + } + + /** + * Control whether methods that change the list ({@link #add}, {@link #addAll(Collection)}, + * {@link #addAll(Object[])}, {@link #insert}, {@link #remove}, {@link #clear}, + * {@link #sort(Comparator)}) automatically call {@link #notifyDataSetChanged}. If set to + * false, caller must manually call notifyDataSetChanged() to have the changes + * reflected in the attached view. + * + * The default is true, and calling notifyDataSetChanged() + * resets the flag to true. + * + * @param notifyOnChange if true, modifications to the list will + * automatically call {@link + * #notifyDataSetChanged} + */ + public void setNotifyOnChange(boolean notifyOnChange) { + mNotifyOnChange = notifyOnChange; + } + + /** + * Returns the context associated with this array adapter. The context is used + * to create views from the resource passed to the constructor. + * + * @return The Context associated with this adapter. + */ + public @NonNull Context getContext() { + return mContext; + } + + @Override + public int getCount() { + return mObjects.size(); + } + + @Override + public @Nullable T getItem(int position) { + return mObjects.get(position); + } + + /** + * Returns the position of the specified item in the array. + * + * @param item The item to retrieve the position of. + * + * @return The position of the specified item. + */ + public int getPosition(@Nullable T item) { + return mObjects.indexOf(item); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public @NonNull View getView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { + return createViewFromResource(mInflater, position, convertView, parent, mResource); + } + + private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position, + @Nullable View convertView, @NonNull ViewGroup parent, int resource) { + final View view; + final TextView text; + + if (convertView == null) { + view = inflater.inflate(resource, parent, false); + } else { + view = convertView; + } + + try { + if (mFieldId == 0) { + // If no custom field is assigned, assume the whole resource is a TextView + text = (TextView) view; + } else { + // Otherwise, find the TextView field within the layout + text = view.findViewById(mFieldId); + + if (text == null) { + throw new RuntimeException("Failed to find view with ID " + + mContext.getResources().getResourceName(mFieldId) + + " in item layout"); + } + } + } catch (ClassCastException e) { + Log.e("ArrayAdapter", "You must supply a resource ID for a TextView"); + throw new IllegalStateException( + "ArrayAdapter requires the resource ID to be a TextView", e); + } + + final T item = getItem(position); + if (item instanceof CharSequence) { + text.setText((CharSequence) item); + } else { + text.setText(item.toString()); + } + + return view; + } + + /** + * <p>Sets the layout resource to create the drop down views.</p> + * + * @param resource the layout resource defining the drop down views + * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) + */ + public void setDropDownViewResource(@LayoutRes int resource) { + this.mDropDownResource = resource; + } + + /** + * Sets the {@link Resources.Theme} against which drop-down views are + * inflated. + * <p> + * By default, drop-down views are inflated against the theme of the + * {@link Context} passed to the adapter's constructor. + * + * @param theme the theme against which to inflate drop-down views or + * {@code null} to use the theme from the adapter's context + * @see #getDropDownView(int, View, ViewGroup) + */ + @Override + public void setDropDownViewTheme(@Nullable Resources.Theme theme) { + if (theme == null) { + mDropDownInflater = null; + } else if (theme == mInflater.getContext().getTheme()) { + mDropDownInflater = mInflater; + } else { + final Context context = new ContextThemeWrapper(mContext, theme); + mDropDownInflater = LayoutInflater.from(context); + } + } + + @Override + public @Nullable Resources.Theme getDropDownViewTheme() { + return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme(); + } + + @Override + public View getDropDownView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { + final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater; + return createViewFromResource(inflater, position, convertView, parent, mDropDownResource); + } + + /** + * Creates a new ArrayAdapter from external resources. The content of the array is + * obtained through {@link android.content.res.Resources#getTextArray(int)}. + * + * @param context The application's environment. + * @param textArrayResId The identifier of the array to use as the data source. + * @param textViewResId The identifier of the layout used to create views. + * + * @return An ArrayAdapter<CharSequence>. + */ + public static @NonNull ArrayAdapter<CharSequence> createFromResource(@NonNull Context context, + @ArrayRes int textArrayResId, @LayoutRes int textViewResId) { + final CharSequence[] strings = context.getResources().getTextArray(textArrayResId); + return new ArrayAdapter<>(context, textViewResId, 0, Arrays.asList(strings), true); + } + + @Override + public @NonNull Filter getFilter() { + if (mFilter == null) { + mFilter = new ArrayFilter(); + } + return mFilter; + } + + /** + * {@inheritDoc} + * + * @return values from the string array used by {@link #createFromResource(Context, int, int)}, + * or {@code null} if object was created otherwsie or if contents were dynamically changed after + * creation. + */ + @Override + public CharSequence[] getAutofillOptions() { + // First check if app developer explicitly set them. + final CharSequence[] explicitOptions = super.getAutofillOptions(); + if (explicitOptions != null) { + return explicitOptions; + } + + // Otherwise, only return options that came from static resources. + if (!mObjectsFromResources || mObjects == null || mObjects.isEmpty()) { + return null; + } + final int size = mObjects.size(); + final CharSequence[] options = new CharSequence[size]; + mObjects.toArray(options); + return options; + } + + /** + * <p>An array filter constrains the content of the array adapter with + * a prefix. Each item that does not start with the supplied prefix + * is removed from the list.</p> + */ + private class ArrayFilter extends Filter { + @Override + protected FilterResults performFiltering(CharSequence prefix) { + final FilterResults results = new FilterResults(); + + if (mOriginalValues == null) { + synchronized (mLock) { + mOriginalValues = new ArrayList<>(mObjects); + } + } + + if (prefix == null || prefix.length() == 0) { + final ArrayList<T> list; + synchronized (mLock) { + list = new ArrayList<>(mOriginalValues); + } + results.values = list; + results.count = list.size(); + } else { + final String prefixString = prefix.toString().toLowerCase(); + + final ArrayList<T> values; + synchronized (mLock) { + values = new ArrayList<>(mOriginalValues); + } + + final int count = values.size(); + final ArrayList<T> newValues = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + final T value = values.get(i); + final String valueText = value.toString().toLowerCase(); + + // First match against the whole, non-splitted value + if (valueText.startsWith(prefixString)) { + newValues.add(value); + } else { + final String[] words = valueText.split(" "); + for (String word : words) { + if (word.startsWith(prefixString)) { + newValues.add(value); + break; + } + } + } + } + + results.values = newValues; + results.count = newValues.size(); + } + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + //noinspection unchecked + mObjects = (List<T>) results.values; + if (results.count > 0) { + notifyDataSetChanged(); + } else { + notifyDataSetInvalidated(); + } + } + } +}