From e522200e28bed5a73e4ff6b85f944897553c7dc3 Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Fri, 1 Sep 2023 12:25:19 +0200 Subject: [PATCH] add android.widget.Adapter and related classes from AOSP --- src/api-impl/android/widget/Adapter.java | 147 ++++++ src/api-impl/android/widget/ArrayAdapter.java | 466 ++++++++++++++++++ src/api-impl/android/widget/BaseAdapter.java | 79 +++ src/api-impl/android/widget/Spinner.java | 10 + .../android/widget/SpinnerAdapter.java | 43 ++ src/api-impl/meson.build | 4 + 6 files changed, 749 insertions(+) create mode 100644 src/api-impl/android/widget/Adapter.java create mode 100644 src/api-impl/android/widget/ArrayAdapter.java create mode 100644 src/api-impl/android/widget/BaseAdapter.java create mode 100644 src/api-impl/android/widget/SpinnerAdapter.java diff --git a/src/api-impl/android/widget/Adapter.java b/src/api-impl/android/widget/Adapter.java new file mode 100644 index 00000000..f204f068 --- /dev/null +++ b/src/api-impl/android/widget/Adapter.java @@ -0,0 +1,147 @@ +/* + * 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.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; + +/** + * An Adapter object acts as a bridge between an {@link AdapterView} and the + * underlying data for that view. The Adapter provides access to the data items. + * The Adapter is also responsible for making a {@link android.view.View} for + * each item in the data set. + * + * @see android.widget.ArrayAdapter + * @see android.widget.CursorAdapter + * @see android.widget.SimpleCursorAdapter + */ +public interface Adapter { + /** + * Register an observer that is called when changes happen to the data used by this adapter. + * + * @param observer the object that gets notified when the data set changes. + */ + void registerDataSetObserver(DataSetObserver observer); + /** + * Unregister an observer that has previously been registered with this + * adapter via {@link #registerDataSetObserver}. + * + * @param observer the object to unregister. + */ + void unregisterDataSetObserver(DataSetObserver observer); + /** + * How many items are in the data set represented by this Adapter. + * + * @return Count of items. + */ + int getCount(); + + /** + * Get the data item associated with the specified position in the data set. + * + * @param position Position of the item whose data we want within the adapter's + * data set. + * @return The data at the specified position. + */ + Object getItem(int position); + + /** + * Get the row id associated with the specified position in the list. + * + * @param position The position of the item within the adapter's data set whose row id we want. + * @return The id of the item at the specified position. + */ + long getItemId(int position); + + /** + * 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. + */ + boolean hasStableIds(); + + /** + * Get a View that displays the data at the specified position in the data set. You can either + * create a View manually or inflate it from an XML layout file. When the View is inflated, the + * parent View (GridView, ListView...) will apply default layout parameters unless you use + * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} + * to specify a root view and to prevent attachment to the root. + * + * @param position The position of the item within the adapter's data set of the item whose view + * we want. + * @param convertView The old view to reuse, if possible. Note: You should check that this view + * is non-null and of an appropriate type before using. If it is not possible to convert + * this view to display the correct data, this method can create a new view. + * Heterogeneous lists can specify their number of view types, so that this View is + * always of the right type (see {@link #getViewTypeCount()} and + * {@link #getItemViewType(int)}). + * @param parent The parent that this view will eventually be attached to + * @return A View corresponding to the data at the specified position. + */ + View getView(int position, View convertView, ViewGroup parent); + /** + * An item view type that causes the {@link AdapterView} to ignore the item + * view. For example, this can be used if the client does not want a + * particular view to be given for conversion in + * {@link #getView(int, View, ViewGroup)}. + * + * @see #getItemViewType(int) + * @see #getViewTypeCount() + */ + static final int IGNORE_ITEM_VIEW_TYPE = /*AdapterView.ITEM_VIEW_TYPE_IGNORE*/ -1; + + /** + * Get the type of View that will be created by {@link #getView} for the specified item. + * + * @param position The position of the item within the adapter's data set whose view type we + * want. + * @return An integer representing the type of View. Two views should share the same type if one + * can be converted to the other in {@link #getView}. Note: Integers must be in the + * range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can + * also be returned. + * @see #IGNORE_ITEM_VIEW_TYPE + */ + int getItemViewType(int position); + + /** + *

+ * Returns the number of types of Views that will be created by + * {@link #getView}. Each type represents a set of views that can be + * converted in {@link #getView}. If the adapter always returns the same + * type of View for all items, this method should return 1. + *

+ *

+ * This method will only be called when when the adapter is set on the + * the {@link AdapterView}. + *

+ * + * @return The number of types of Views that will be created by this adapter + */ + int getViewTypeCount(); + + static final int NO_SELECTION = Integer.MIN_VALUE; + + /** + * @return true if this adapter doesn't contain any data. This is used to determine + * whether the empty view should be displayed. A typical implementation will return + * getCount() == 0 but since getCount() includes the headers and footers, specialized + * adapters might want a different behavior. + */ + boolean isEmpty(); +} diff --git a/src/api-impl/android/widget/ArrayAdapter.java b/src/api-impl/android/widget/ArrayAdapter.java new file mode 100644 index 00000000..de979aa2 --- /dev/null +++ b/src/api-impl/android/widget/ArrayAdapter.java @@ -0,0 +1,466 @@ +/* + * 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.content.Context; +import android.util.Log; +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; + +/** + * A concrete BaseAdapter that is backed by an array of arbitrary + * objects. By default this class expects that the provided resource id references + * a single TextView. If you want to use a more complex layout, use the constructors that + * also takes a field id. That field id should reference a TextView in the larger layout + * resource. + * + *

However the TextView is referenced, it will be filled with the toString() of each object in + * the array. You can add lists or arrays of custom objects. Override the toString() method + * of your objects to determine what text will be displayed for the item in the list. + * + *

To use something other than TextViews for the array display, for instance, ImageViews, + * or to have some of data besides toString() results fill the views, + * override {@link #getView(int, View, ViewGroup)} to return the type of view you want. + */ +public class ArrayAdapter extends BaseAdapter /*implements Filterable*/ { + /** + * 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. + */ + private List mObjects; + /** + * 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. + */ + private final Object mLock = new Object(); + /** + * The resource indicating what views to inflate to display the content of this + * array adapter. + */ + private 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; + /** + * If the inflated resource is not a TextView, {@link #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; + private Context mContext; + // 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. + private ArrayList mOriginalValues; + private ArrayFilter mFilter; + private LayoutInflater mInflater; + /** + * Constructor + * + * @param context The current context. + * @param textViewResourceId The resource ID for a layout file containing a TextView to use when + * instantiating views. + */ + public ArrayAdapter(Context context, int textViewResourceId) { + init(context, textViewResourceId, 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(Context context, int resource, int textViewResourceId) { + init(context, resource, textViewResourceId, new ArrayList()); + } + /** + * Constructor + * + * @param context The current context. + * @param textViewResourceId 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(Context context, int textViewResourceId, T[] objects) { + init(context, textViewResourceId, 0, Arrays.asList(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(Context context, int resource, int textViewResourceId, T[] objects) { + init(context, resource, textViewResourceId, Arrays.asList(objects)); + } + /** + * Constructor + * + * @param context The current context. + * @param textViewResourceId 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(Context context, int textViewResourceId, List objects) { + init(context, textViewResourceId, 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(Context context, int resource, int textViewResourceId, List objects) { + init(context, resource, textViewResourceId, objects); + } + /** + * Adds the specified object at the end of the array. + * + * @param object The object to add at the end of the array. + */ + public void add(T object) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.add(object); + } else { + mObjects.add(object); + } + } + 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. + */ + public void addAll(Collection collection) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.addAll(collection); + } else { + mObjects.addAll(collection); + } + } + 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. + */ + public void addAll(T ... items) { + synchronized (mLock) { + if (mOriginalValues != null) { + Collections.addAll(mOriginalValues, items); + } else { + Collections.addAll(mObjects, items); + } + } + 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. + */ + public void insert(T object, int index) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.add(index, object); + } else { + mObjects.add(index, object); + } + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + /** + * Removes the specified object from the array. + * + * @param object The object to remove. + */ + public void remove(T object) { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.remove(object); + } else { + mObjects.remove(object); + } + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + /** + * Remove all elements from the list. + */ + public void clear() { + synchronized (mLock) { + if (mOriginalValues != null) { + mOriginalValues.clear(); + } else { + mObjects.clear(); + } + } + 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(Comparator comparator) { + synchronized (mLock) { + if (mOriginalValues != null) { + Collections.sort(mOriginalValues, comparator); + } else { + Collections.sort(mObjects, comparator); + } + } + if (mNotifyOnChange) notifyDataSetChanged(); + } + /** + * {@inheritDoc} + */ + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + mNotifyOnChange = true; + } + /** + * Control whether methods that change the list ({@link #add}, + * {@link #insert}, {@link #remove}, {@link #clear}) 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; + } + private void init(Context context, int resource, int textViewResourceId, List objects) { + mContext = context; + mInflater = new LayoutInflater(); + mResource = mDropDownResource = resource; + mObjects = objects; + mFieldId = textViewResourceId; + } + /** + * 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 Context getContext() { + return mContext; + } + /** + * {@inheritDoc} + */ + public int getCount() { + return mObjects.size(); + } + /** + * {@inheritDoc} + */ + public 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(T item) { + return mObjects.indexOf(item); + } + /** + * {@inheritDoc} + */ + public long getItemId(int position) { + return position; + } + /** + * {@inheritDoc} + */ + public View getView(int position, View convertView, ViewGroup parent) { + return createViewFromResource(position, convertView, parent, mResource); + } + private View createViewFromResource(int position, View convertView, ViewGroup parent, + int resource) { + View view; + TextView text; + if (convertView == null) { + view = mInflater.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 = (TextView) view.findViewById(mFieldId); + } + } 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); + } + T item = getItem(position); + if (item instanceof CharSequence) { + text.setText((CharSequence)item); + } else { + text.setText(item.toString()); + } + return view; + } + /** + *

Sets the layout resource to create the drop down views.

+ * + * @param resource the layout resource defining the drop down views + * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) + */ + public void setDropDownViewResource(int resource) { + this.mDropDownResource = resource; + } + /** + * {@inheritDoc} + */ + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return createViewFromResource(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. + */ + public static ArrayAdapter createFromResource(Context context, + int textArrayResId, int textViewResId) { + CharSequence[] strings = context.getResources().getTextArray(textArrayResId); + return new ArrayAdapter(context, textViewResId, strings); + } + /** + * {@inheritDoc} + */ + public Filter getFilter() { + if (mFilter == null) { + mFilter = new ArrayFilter(); + } + return mFilter; + } + /** + *

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.

+ */ + private class ArrayFilter extends Filter { + @Override + protected FilterResults performFiltering(CharSequence prefix) { + FilterResults results = new FilterResults(); + if (mOriginalValues == null) { + synchronized (mLock) { + mOriginalValues = new ArrayList(mObjects); + } + } + if (prefix == null || prefix.length() == 0) { + ArrayList list; + synchronized (mLock) { + list = new ArrayList(mOriginalValues); + } + results.values = list; + results.count = list.size(); + } else { + String prefixString = prefix.toString().toLowerCase(); + ArrayList values; + synchronized (mLock) { + values = new ArrayList(mOriginalValues); + } + final int count = values.size(); + final ArrayList 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(" "); + final int wordCount = words.length; + // Start at index 0, in case valueText starts with space(s) + for (int k = 0; k < wordCount; k++) { + if (words[k].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) results.values; + if (results.count > 0) { + notifyDataSetChanged(); + } else { + notifyDataSetInvalidated(); + } + } + } +} diff --git a/src/api-impl/android/widget/BaseAdapter.java b/src/api-impl/android/widget/BaseAdapter.java new file mode 100644 index 00000000..837f0a36 --- /dev/null +++ b/src/api-impl/android/widget/BaseAdapter.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 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.database.DataSetObservable; +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; + +/** + * Common base class of common implementation for an {@link Adapter} that can be + * used in both {@link ListView} (by implementing the specialized + * {@link ListAdapter} interface} and {@link Spinner} (by implementing the + * specialized {@link SpinnerAdapter} interface. + */ +public abstract class BaseAdapter implements /*ListAdapter,*/ SpinnerAdapter { + private final DataSetObservable mDataSetObservable = new DataSetObservable(); + public boolean hasStableIds() { + return false; + } + + public void registerDataSetObserver(DataSetObserver observer) { + mDataSetObservable.registerObserver(observer); + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + mDataSetObservable.unregisterObserver(observer); + } + + /** + * Notifies the attached observers that the underlying data has been changed + * and any View reflecting the data set should refresh itself. + */ + public void notifyDataSetChanged() { + mDataSetObservable.notifyChanged(); + } + + /** + * Notifies the attached observers that the underlying data is no longer valid + * or available. Once invoked this adapter is no longer valid and should + * not report further data set changes. + */ + public void notifyDataSetInvalidated() { + mDataSetObservable.notifyInvalidated(); + } + public boolean areAllItemsEnabled() { + return true; + } + public boolean isEnabled(int position) { + return true; + } + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return getView(position, convertView, parent); + } + public int getItemViewType(int position) { + return 0; + } + public int getViewTypeCount() { + return 1; + } + + public boolean isEmpty() { + return getCount() == 0; + } +} diff --git a/src/api-impl/android/widget/Spinner.java b/src/api-impl/android/widget/Spinner.java index 78dd1532..bb0b3f64 100644 --- a/src/api-impl/android/widget/Spinner.java +++ b/src/api-impl/android/widget/Spinner.java @@ -14,4 +14,14 @@ public class Spinner extends View { super(context, attributeSet); } + public void setAdapter(SpinnerAdapter adapter) { + } + + public void setSelection(int i) { + } + + public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {} + + public void setSelection(int position, boolean animate) {} + } \ No newline at end of file diff --git a/src/api-impl/android/widget/SpinnerAdapter.java b/src/api-impl/android/widget/SpinnerAdapter.java new file mode 100644 index 00000000..975f3396 --- /dev/null +++ b/src/api-impl/android/widget/SpinnerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 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.view.View; +import android.view.ViewGroup; + +/** + * Extended {@link Adapter} that is the bridge between a + * {@link android.widget.Spinner} and its data. A spinner adapter allows to + * define two different views: one that shows the data in the spinner itself and + * one that shows the data in the drop down list when the spinner is pressed.

+ */ +public interface SpinnerAdapter extends Adapter { + /** + *

Get a {@link android.view.View} that displays in the drop down popup + * the data at the specified position in the data set.

+ * + * @param position index of the item whose view we want. + * @param convertView the old view to reuse, if possible. Note: You should + * check that this view is non-null and of an appropriate type before + * using. If it is not possible to convert this view to display the + * correct data, this method can create a new view. + * @param parent the parent that this view will eventually be attached to + * @return a {@link android.view.View} corresponding to the data at the + * specified position. + */ + public View getDropDownView(int position, View convertView, ViewGroup parent); +} diff --git a/src/api-impl/meson.build b/src/api-impl/meson.build index a12bcb67..953f973f 100644 --- a/src/api-impl/meson.build +++ b/src/api-impl/meson.build @@ -329,8 +329,11 @@ hax_jar = jar('hax', [ 'android/webkit/WebSettings.java', 'android/webkit/WebView.java', 'android/webkit/WebViewClient.java', + 'android/widget/Adapter.java', 'android/widget/AdapterView.java', + 'android/widget/ArrayAdapter.java', 'android/widget/AutoCompleteTextView.java', + 'android/widget/BaseAdapter.java', 'android/widget/Button.java', 'android/widget/Checkable.java', 'android/widget/CompoundButton.java', @@ -352,6 +355,7 @@ hax_jar = jar('hax', [ 'android/widget/ScrollView.java', 'android/widget/SeekBar.java', 'android/widget/Spinner.java', + 'android/widget/SpinnerAdapter.java', 'android/widget/TextView.java', 'com/android/internal/Manifest.java', 'com/android/internal/R.java',