diff --git a/src/api-impl/android/view/SoundEffectConstants.java b/src/api-impl/android/view/SoundEffectConstants.java
index dc1752b1..177f54ab 100644
--- a/src/api-impl/android/view/SoundEffectConstants.java
+++ b/src/api-impl/android/view/SoundEffectConstants.java
@@ -2,6 +2,8 @@ package android.view;
public class SoundEffectConstants {
+ public static final int CLICK = 0;
+
// the typo is part of the API
public static int getContantForFocusDirection(int direction) {
return 0;
diff --git a/src/api-impl/android/widget/BaseExpandableListAdapter.java b/src/api-impl/android/widget/BaseExpandableListAdapter.java
new file mode 100644
index 00000000..a9bec532
--- /dev/null
+++ b/src/api-impl/android/widget/BaseExpandableListAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+/**
+ * Base class for a {@link ExpandableListAdapter} used to provide data and Views
+ * from some data to an expandable list view.
+ *
+ * Adapters inheriting this class should verify that the base implementations of
+ * {@link #getCombinedChildId(long, long)} and {@link #getCombinedGroupId(long)}
+ * are correct in generating unique IDs from the group/children IDs.
+ *
+ * @see SimpleExpandableListAdapter
+ * @see SimpleCursorTreeAdapter
+ */
+public abstract class BaseExpandableListAdapter implements ExpandableListAdapter,
+ HeterogeneousExpandableList {
+ private final DataSetObservable mDataSetObservable = new DataSetObservable();
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.registerObserver(observer);
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * @see DataSetObservable#notifyInvalidated()
+ */
+ public void notifyDataSetInvalidated() {
+ mDataSetObservable.notifyInvalidated();
+ }
+
+ /**
+ * @see DataSetObservable#notifyChanged()
+ */
+ public void notifyDataSetChanged() {
+ mDataSetObservable.notifyChanged();
+ }
+
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ public void onGroupCollapsed(int groupPosition) {
+ }
+
+ public void onGroupExpanded(int groupPosition) {
+ }
+
+ /**
+ * Override this method if you foresee a clash in IDs based on this scheme:
+ *
cPos - Child position, the position of a child among all the children
+ * in a group
+ */
+
+/**
+ * A {@link BaseAdapter} that provides data/Views in an expandable list (offers
+ * features such as collapsing/expanding groups containing children). By
+ * itself, this adapter has no data and is a connector to a
+ * {@link ExpandableListAdapter} which provides the data.
+ *
+ * Internally, this connector translates the flat list position that the
+ * ListAdapter expects to/from group and child positions that the ExpandableListAdapter
+ * expects.
+ */
+class ExpandableListConnector extends BaseAdapter implements Filterable {
+ /**
+ * The ExpandableListAdapter to fetch the data/Views for this expandable list
+ */
+ private ExpandableListAdapter mExpandableListAdapter;
+
+ /**
+ * List of metadata for the currently expanded groups. The metadata consists
+ * of data essential for efficiently translating between flat list positions
+ * and group/child positions. See {@link GroupMetadata}.
+ */
+ private ArrayList mExpGroupMetadataList;
+
+ /**
+ * The number of children from all currently expanded groups
+ */
+ private int mTotalExpChildrenCount;
+
+ /**
+ * The maximum number of allowable expanded groups. Defaults to 'no limit'
+ */
+ private int mMaxExpGroupCount = Integer.MAX_VALUE;
+
+ /**
+ * Change observer used to have ExpandableListAdapter changes pushed to us
+ */
+ private final DataSetObserver mDataSetObserver = new MyDataSetObserver();
+
+ /**
+ * Constructs the connector
+ */
+ public ExpandableListConnector(ExpandableListAdapter expandableListAdapter) {
+ mExpGroupMetadataList = new ArrayList();
+
+ setExpandableListAdapter(expandableListAdapter);
+ }
+
+ /**
+ * Point to the {@link ExpandableListAdapter} that will give us data/Views
+ *
+ * @param expandableListAdapter the adapter that supplies us with data/Views
+ */
+ public void setExpandableListAdapter(ExpandableListAdapter expandableListAdapter) {
+ if (mExpandableListAdapter != null) {
+ mExpandableListAdapter.unregisterDataSetObserver(mDataSetObserver);
+ }
+
+ mExpandableListAdapter = expandableListAdapter;
+ expandableListAdapter.registerDataSetObserver(mDataSetObserver);
+ }
+
+ /**
+ * Translates a flat list position to either a) group pos if the specified
+ * flat list position corresponds to a group, or b) child pos if it
+ * corresponds to a child. Performs a binary search on the expanded
+ * groups list to find the flat list pos if it is an exp group, otherwise
+ * finds where the flat list pos fits in between the exp groups.
+ *
+ * @param flPos the flat list position to be translated
+ * @return the group position or child position of the specified flat list
+ * position encompassed in a {@link PositionMetadata} object
+ * that contains additional useful info for insertion, etc.
+ */
+ PositionMetadata getUnflattenedPos(final int flPos) {
+ /* Keep locally since frequent use */
+ final ArrayList egml = mExpGroupMetadataList;
+ final int numExpGroups = egml.size();
+
+ /* Binary search variables */
+ int leftExpGroupIndex = 0;
+ int rightExpGroupIndex = numExpGroups - 1;
+ int midExpGroupIndex = 0;
+ GroupMetadata midExpGm;
+
+ if (numExpGroups == 0) {
+ /*
+ * There aren't any expanded groups (hence no visible children
+ * either), so flPos must be a group and its group pos will be the
+ * same as its flPos
+ */
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos,
+ -1, null, 0);
+ }
+
+ /*
+ * Binary search over the expanded groups to find either the exact
+ * expanded group (if we're looking for a group) or the group that
+ * contains the child we're looking for. If we are looking for a
+ * collapsed group, we will not have a direct match here, but we will
+ * find the expanded group just before the group we're searching for (so
+ * then we can calculate the group position of the group we're searching
+ * for). If there isn't an expanded group prior to the group being
+ * searched for, then the group being searched for's group position is
+ * the same as the flat list position (since there are no children before
+ * it, and all groups before it are collapsed).
+ */
+ while (leftExpGroupIndex <= rightExpGroupIndex) {
+ midExpGroupIndex =
+ (rightExpGroupIndex - leftExpGroupIndex) / 2 + leftExpGroupIndex;
+ midExpGm = egml.get(midExpGroupIndex);
+
+ if (flPos > midExpGm.lastChildFlPos) {
+ /*
+ * The flat list position is after the current middle group's
+ * last child's flat list position, so search right
+ */
+ leftExpGroupIndex = midExpGroupIndex + 1;
+ } else if (flPos < midExpGm.flPos) {
+ /*
+ * The flat list position is before the current middle group's
+ * flat list position, so search left
+ */
+ rightExpGroupIndex = midExpGroupIndex - 1;
+ } else if (flPos == midExpGm.flPos) {
+ /*
+ * The flat list position is this middle group's flat list
+ * position, so we've found an exact hit
+ */
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP,
+ midExpGm.gPos, -1, midExpGm, midExpGroupIndex);
+ } else if (flPos <= midExpGm.lastChildFlPos
+ /* && flPos > midGm.flPos as deduced from previous
+ * conditions */
+ ) {
+ /* The flat list position is a child of the middle group */
+
+ /*
+ * Subtract the first child's flat list position from the
+ * specified flat list pos to get the child's position within
+ * the group
+ */
+ final int childPos = flPos - (midExpGm.flPos + 1);
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD,
+ midExpGm.gPos, childPos, midExpGm, midExpGroupIndex);
+ }
+ }
+
+ /*
+ * If we've reached here, it means the flat list position must be a
+ * group that is not expanded, since otherwise we would have hit it
+ * in the above search.
+ */
+
+ /**
+ * If we are to expand this group later, where would it go in the
+ * mExpGroupMetadataList ?
+ */
+ int insertPosition = 0;
+
+ /**
+ * What is its group position in the list of all groups?
+ */
+ int groupPos = 0;
+
+ /*
+ * To figure out exact insertion and prior group positions, we need to
+ * determine how we broke out of the binary search. We backtrack
+ * to see this.
+ */
+ if (leftExpGroupIndex > midExpGroupIndex) {
+
+ /*
+ * This would occur in the first conditional, so the flat list
+ * insertion position is after the left group. Also, the
+ * leftGroupPos is one more than it should be (since that broke out
+ * of our binary search), so we decrement it.
+ */
+ final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex - 1);
+
+ insertPosition = leftExpGroupIndex;
+
+ /*
+ * Sums the number of groups between the prior exp group and this
+ * one, and then adds it to the prior group's group pos
+ */
+ groupPos =
+ (flPos - leftExpGm.lastChildFlPos) + leftExpGm.gPos;
+ } else if (rightExpGroupIndex < midExpGroupIndex) {
+
+ /*
+ * This would occur in the second conditional, so the flat list
+ * insertion position is before the right group. Also, the
+ * rightGroupPos is one less than it should be, so increment it.
+ */
+ final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex);
+
+ insertPosition = rightExpGroupIndex;
+
+ /*
+ * Subtracts this group's flat list pos from the group after's flat
+ * list position to find out how many groups are in between the two
+ * groups. Then, subtracts that number from the group after's group
+ * pos to get this group's pos.
+ */
+ groupPos = rightExpGm.gPos - (rightExpGm.flPos - flPos);
+ } else {
+ // TODO: clean exit
+ throw new RuntimeException("Unknown state");
+ }
+
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1,
+ null, insertPosition);
+ }
+
+ /**
+ * Translates either a group pos or a child pos (+ group it belongs to) to a
+ * flat list position. If searching for a child and its group is not expanded, this will
+ * return null since the child isn't being shown in the ListView, and hence it has no
+ * position.
+ *
+ * @param pos a {@link ExpandableListPosition} representing either a group position
+ * or child position
+ * @return the flat list position encompassed in a {@link PositionMetadata}
+ * object that contains additional useful info for insertion, etc., or null.
+ */
+ PositionMetadata getFlattenedPos(final ExpandableListPosition pos) {
+ final ArrayList egml = mExpGroupMetadataList;
+ final int numExpGroups = egml.size();
+
+ /* Binary search variables */
+ int leftExpGroupIndex = 0;
+ int rightExpGroupIndex = numExpGroups - 1;
+ int midExpGroupIndex = 0;
+ GroupMetadata midExpGm;
+
+ if (numExpGroups == 0) {
+ /*
+ * There aren't any expanded groups, so flPos must be a group and
+ * its flPos will be the same as its group pos. The
+ * insert position is 0 (since the list is empty).
+ */
+ return PositionMetadata.obtain(pos.groupPos, pos.type,
+ pos.groupPos, pos.childPos, null, 0);
+ }
+
+ /*
+ * Binary search over the expanded groups to find either the exact
+ * expanded group (if we're looking for a group) or the group that
+ * contains the child we're looking for.
+ */
+ while (leftExpGroupIndex <= rightExpGroupIndex) {
+ midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex) / 2 + leftExpGroupIndex;
+ midExpGm = egml.get(midExpGroupIndex);
+
+ if (pos.groupPos > midExpGm.gPos) {
+ /*
+ * It's after the current middle group, so search right
+ */
+ leftExpGroupIndex = midExpGroupIndex + 1;
+ } else if (pos.groupPos < midExpGm.gPos) {
+ /*
+ * It's before the current middle group, so search left
+ */
+ rightExpGroupIndex = midExpGroupIndex - 1;
+ } else if (pos.groupPos == midExpGm.gPos) {
+ /*
+ * It's this middle group, exact hit
+ */
+
+ if (pos.type == ExpandableListPosition.GROUP) {
+ /* If it's a group, give them this matched group's flPos */
+ return PositionMetadata.obtain(midExpGm.flPos, pos.type,
+ pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex);
+ } else if (pos.type == ExpandableListPosition.CHILD) {
+ /* If it's a child, calculate the flat list pos */
+ return PositionMetadata.obtain(midExpGm.flPos + pos.childPos + 1, pos.type, pos.groupPos, pos.childPos,
+ midExpGm, midExpGroupIndex);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /*
+ * If we've reached here, it means there was no match in the expanded
+ * groups, so it must be a collapsed group that they're search for
+ */
+ if (pos.type != ExpandableListPosition.GROUP) {
+ /* If it isn't a group, return null */
+ return null;
+ }
+
+ /*
+ * To figure out exact insertion and prior group positions, we need to
+ * determine how we broke out of the binary search. We backtrack to see
+ * this.
+ */
+ if (leftExpGroupIndex > midExpGroupIndex) {
+
+ /*
+ * This would occur in the first conditional, so the flat list
+ * insertion position is after the left group.
+ *
+ * The leftGroupPos is one more than it should be (from the binary
+ * search loop) so we subtract 1 to get the actual left group. Since
+ * the insertion point is AFTER the left group, we keep this +1
+ * value as the insertion point
+ */
+ final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex - 1);
+ final int flPos =
+ leftExpGm.lastChildFlPos + (pos.groupPos - leftExpGm.gPos);
+
+ return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
+ pos.childPos, null, leftExpGroupIndex);
+ } else if (rightExpGroupIndex < midExpGroupIndex) {
+
+ /*
+ * This would occur in the second conditional, so the flat list
+ * insertion position is before the right group. Also, the
+ * rightGroupPos is one less than it should be (from binary search
+ * loop), so we increment to it.
+ */
+ final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex);
+ final int flPos =
+ rightExpGm.flPos - (rightExpGm.gPos - pos.groupPos);
+ return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
+ pos.childPos, null, rightExpGroupIndex);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return mExpandableListAdapter.areAllItemsEnabled();
+ }
+
+ @Override
+ public boolean isEnabled(int flatListPos) {
+ final PositionMetadata metadata = getUnflattenedPos(flatListPos);
+ final ExpandableListPosition pos = metadata.position;
+
+ boolean retValue;
+ if (pos.type == ExpandableListPosition.CHILD) {
+ retValue = mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos);
+ } else {
+ // Groups are always selectable
+ retValue = true;
+ }
+
+ metadata.recycle();
+
+ return retValue;
+ }
+
+ public int getCount() {
+ /*
+ * Total count for the list view is the number groups plus the
+ * number of children from currently expanded groups (a value we keep
+ * cached in this class)
+ */
+ return mExpandableListAdapter.getGroupCount() + mTotalExpChildrenCount;
+ }
+
+ public Object getItem(int flatListPos) {
+ final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+
+ Object retValue;
+ if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+ retValue = mExpandableListAdapter
+ .getGroup(posMetadata.position.groupPos);
+ } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+ retValue = mExpandableListAdapter.getChild(posMetadata.position.groupPos,
+ posMetadata.position.childPos);
+ } else {
+ // TODO: clean exit
+ throw new RuntimeException("Flat list position is of unknown type");
+ }
+
+ posMetadata.recycle();
+
+ return retValue;
+ }
+
+ public long getItemId(int flatListPos) {
+ final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+ final long groupId = mExpandableListAdapter.getGroupId(posMetadata.position.groupPos);
+
+ long retValue;
+ if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+ retValue = mExpandableListAdapter.getCombinedGroupId(groupId);
+ } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+ final long childId = mExpandableListAdapter.getChildId(posMetadata.position.groupPos,
+ posMetadata.position.childPos);
+ retValue = mExpandableListAdapter.getCombinedChildId(groupId, childId);
+ } else {
+ // TODO: clean exit
+ throw new RuntimeException("Flat list position is of unknown type");
+ }
+
+ posMetadata.recycle();
+
+ return retValue;
+ }
+
+ public View getView(int flatListPos, View convertView, ViewGroup parent) {
+ final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+
+ View retValue;
+ if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+ retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos,
+ posMetadata.isExpanded(), convertView, parent);
+ } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+ final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
+
+ retValue = mExpandableListAdapter.getChildView(posMetadata.position.groupPos,
+ posMetadata.position.childPos, isLastChild, convertView, parent);
+ } else {
+ // TODO: clean exit
+ throw new RuntimeException("Flat list position is of unknown type");
+ }
+
+ posMetadata.recycle();
+
+ return retValue;
+ }
+
+ @Override
+ public int getItemViewType(int flatListPos) {
+ final PositionMetadata metadata = getUnflattenedPos(flatListPos);
+ final ExpandableListPosition pos = metadata.position;
+
+ int retValue;
+ if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+ HeterogeneousExpandableList adapter =
+ (HeterogeneousExpandableList)mExpandableListAdapter;
+ if (pos.type == ExpandableListPosition.GROUP) {
+ retValue = adapter.getGroupType(pos.groupPos);
+ } else {
+ final int childType = adapter.getChildType(pos.groupPos, pos.childPos);
+ retValue = adapter.getGroupTypeCount() + childType;
+ }
+ } else {
+ if (pos.type == ExpandableListPosition.GROUP) {
+ retValue = 0;
+ } else {
+ retValue = 1;
+ }
+ }
+
+ metadata.recycle();
+
+ return retValue;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+ HeterogeneousExpandableList adapter =
+ (HeterogeneousExpandableList)mExpandableListAdapter;
+ return adapter.getGroupTypeCount() + adapter.getChildTypeCount();
+ } else {
+ return 2;
+ }
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return mExpandableListAdapter.hasStableIds();
+ }
+
+ /**
+ * Traverses the expanded group metadata list and fills in the flat list
+ * positions.
+ *
+ * @param forceChildrenCountRefresh Forces refreshing of the children count
+ * for all expanded groups.
+ * @param syncGroupPositions Whether to search for the group positions
+ * based on the group IDs. This should only be needed when calling
+ * this from an onChanged callback.
+ */
+ @SuppressWarnings("unchecked")
+ private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh,
+ boolean syncGroupPositions) {
+ final ArrayList egml = mExpGroupMetadataList;
+ int egmlSize = egml.size();
+ int curFlPos = 0;
+
+ /* Update child count as we go through */
+ mTotalExpChildrenCount = 0;
+
+ if (syncGroupPositions) {
+ // We need to check whether any groups have moved positions
+ boolean positionsChanged = false;
+
+ for (int i = egmlSize - 1; i >= 0; i--) {
+ GroupMetadata curGm = egml.get(i);
+ int newGPos = findGroupPosition(curGm.gId, curGm.gPos);
+ if (newGPos != curGm.gPos) {
+ if (newGPos == AdapterView.INVALID_POSITION) {
+ // Doh, just remove it from the list of expanded groups
+ egml.remove(i);
+ egmlSize--;
+ }
+
+ curGm.gPos = newGPos;
+ if (!positionsChanged)
+ positionsChanged = true;
+ }
+ }
+
+ if (positionsChanged) {
+ // At least one group changed positions, so re-sort
+ Collections.sort(egml);
+ }
+ }
+
+ int gChildrenCount;
+ int lastGPos = 0;
+ for (int i = 0; i < egmlSize; i++) {
+ /* Store in local variable since we'll access freq */
+ GroupMetadata curGm = egml.get(i);
+
+ /*
+ * Get the number of children, try to refrain from calling
+ * another class's method unless we have to (so do a subtraction)
+ */
+ if ((curGm.lastChildFlPos == GroupMetadata.REFRESH) || forceChildrenCountRefresh) {
+ gChildrenCount = mExpandableListAdapter.getChildrenCount(curGm.gPos);
+ } else {
+ /* Num children for this group is its last child's fl pos minus
+ * the group's fl pos
+ */
+ gChildrenCount = curGm.lastChildFlPos - curGm.flPos;
+ }
+
+ /* Update */
+ mTotalExpChildrenCount += gChildrenCount;
+
+ /*
+ * This skips the collapsed groups and increments the flat list
+ * position (for subsequent exp groups) by accounting for the collapsed
+ * groups
+ */
+ curFlPos += (curGm.gPos - lastGPos);
+ lastGPos = curGm.gPos;
+
+ /* Update the flat list positions, and the current flat list pos */
+ curGm.flPos = curFlPos;
+ curFlPos += gChildrenCount;
+ curGm.lastChildFlPos = curFlPos;
+ }
+ }
+
+ /**
+ * Collapse a group in the grouped list view
+ *
+ * @param groupPos position of the group to collapse
+ */
+ boolean collapseGroup(int groupPos) {
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
+ if (pm == null)
+ return false;
+
+ boolean retValue = collapseGroup(pm);
+ pm.recycle();
+ return retValue;
+ }
+
+ boolean collapseGroup(PositionMetadata posMetadata) {
+ /*
+ * Collapsing requires removal from mExpGroupMetadataList
+ */
+
+ /*
+ * If it is null, it must be already collapsed. This group metadata
+ * object should have been set from the search that returned the
+ * position metadata object.
+ */
+ if (posMetadata.groupMetadata == null)
+ return false;
+
+ // Remove the group from the list of expanded groups
+ mExpGroupMetadataList.remove(posMetadata.groupMetadata);
+
+ // Refresh the metadata
+ refreshExpGroupMetadataList(false, false);
+
+ // Notify of change
+ notifyDataSetChanged();
+
+ // Give the callback
+ mExpandableListAdapter.onGroupCollapsed(posMetadata.groupMetadata.gPos);
+
+ return true;
+ }
+
+ /**
+ * Expand a group in the grouped list view
+ * @param groupPos the group to be expanded
+ */
+ boolean expandGroup(int groupPos) {
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
+ boolean retValue = expandGroup(pm);
+ pm.recycle();
+ return retValue;
+ }
+
+ boolean expandGroup(PositionMetadata posMetadata) {
+ /*
+ * Expanding requires insertion into the mExpGroupMetadataList
+ */
+
+ if (posMetadata.position.groupPos < 0) {
+ // TODO clean exit
+ throw new RuntimeException("Need group");
+ }
+
+ if (mMaxExpGroupCount == 0)
+ return false;
+
+ // Check to see if it's already expanded
+ if (posMetadata.groupMetadata != null)
+ return false;
+
+ /* Restrict number of expanded groups to mMaxExpGroupCount */
+ if (mExpGroupMetadataList.size() >= mMaxExpGroupCount) {
+ /* Collapse a group */
+ // TODO: Collapse something not on the screen instead of the first one?
+ // TODO: Could write overloaded function to take GroupMetadata to collapse
+ GroupMetadata collapsedGm = mExpGroupMetadataList.get(0);
+
+ int collapsedIndex = mExpGroupMetadataList.indexOf(collapsedGm);
+
+ collapseGroup(collapsedGm.gPos);
+
+ /* Decrement index if it is after the group we removed */
+ if (posMetadata.groupInsertIndex > collapsedIndex) {
+ posMetadata.groupInsertIndex--;
+ }
+ }
+
+ GroupMetadata expandedGm = GroupMetadata.obtain(
+ GroupMetadata.REFRESH,
+ GroupMetadata.REFRESH,
+ posMetadata.position.groupPos,
+ mExpandableListAdapter.getGroupId(posMetadata.position.groupPos));
+
+ mExpGroupMetadataList.add(posMetadata.groupInsertIndex, expandedGm);
+
+ // Refresh the metadata
+ refreshExpGroupMetadataList(false, false);
+
+ // Notify of change
+ notifyDataSetChanged();
+
+ // Give the callback
+ mExpandableListAdapter.onGroupExpanded(expandedGm.gPos);
+
+ return true;
+ }
+
+ /**
+ * Whether the given group is currently expanded.
+ * @param groupPosition The group to check.
+ * @return Whether the group is currently expanded.
+ */
+ public boolean isGroupExpanded(int groupPosition) {
+ GroupMetadata groupMetadata;
+ for (int i = mExpGroupMetadataList.size() - 1; i >= 0; i--) {
+ groupMetadata = mExpGroupMetadataList.get(i);
+
+ if (groupMetadata.gPos == groupPosition) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the maximum number of groups that can be expanded at any given time
+ */
+ public void setMaxExpGroupCount(int maxExpGroupCount) {
+ mMaxExpGroupCount = maxExpGroupCount;
+ }
+
+ ExpandableListAdapter getAdapter() {
+ return mExpandableListAdapter;
+ }
+
+ public Filter getFilter() {
+ ExpandableListAdapter adapter = getAdapter();
+ if (adapter instanceof Filterable) {
+ return ((Filterable)adapter).getFilter();
+ } else {
+ return null;
+ }
+ }
+
+ ArrayList getExpandedGroupMetadataList() {
+ return mExpGroupMetadataList;
+ }
+
+ void setExpandedGroupMetadataList(ArrayList expandedGroupMetadataList) {
+
+ if ((expandedGroupMetadataList == null) || (mExpandableListAdapter == null)) {
+ return;
+ }
+
+ // Make sure our current data set is big enough for the previously
+ // expanded groups, if not, ignore this request
+ int numGroups = mExpandableListAdapter.getGroupCount();
+ for (int i = expandedGroupMetadataList.size() - 1; i >= 0; i--) {
+ if (expandedGroupMetadataList.get(i).gPos >= numGroups) {
+ // Doh, for some reason the client doesn't have some of the groups
+ return;
+ }
+ }
+
+ mExpGroupMetadataList = expandedGroupMetadataList;
+ refreshExpGroupMetadataList(true, false);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ ExpandableListAdapter adapter = getAdapter();
+ return adapter != null ? adapter.isEmpty() : true;
+ }
+
+ /**
+ * Searches the expandable list adapter for a group position matching the
+ * given group ID. The search starts at the given seed position and then
+ * alternates between moving up and moving down until 1) we find the right
+ * position, or 2) we run out of time, or 3) we have looked at every
+ * position
+ *
+ * @return Position of the row that matches the given row ID, or
+ * {@link AdapterView#INVALID_POSITION} if it can't be found
+ * @see AdapterView#findSyncPosition()
+ */
+ int findGroupPosition(long groupIdToMatch, int seedGroupPosition) {
+ int count = mExpandableListAdapter.getGroupCount();
+
+ if (count == 0) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ // If there isn't a selection don't hunt for it
+ if (groupIdToMatch == AdapterView.INVALID_ROW_ID) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ // Pin seed to reasonable values
+ seedGroupPosition = Math.max(0, seedGroupPosition);
+ seedGroupPosition = Math.min(count - 1, seedGroupPosition);
+
+ long endTime = SystemClock.uptimeMillis() + AdapterView.SYNC_MAX_DURATION_MILLIS;
+
+ long rowId;
+
+ // first position scanned so far
+ int first = seedGroupPosition;
+
+ // last position scanned so far
+ int last = seedGroupPosition;
+
+ // True if we should move down on the next iteration
+ boolean next = false;
+
+ // True when we have looked at the first item in the data
+ boolean hitFirst;
+
+ // True when we have looked at the last item in the data
+ boolean hitLast;
+
+ // Get the item ID locally (instead of getItemIdAtPosition), so
+ // we need the adapter
+ ExpandableListAdapter adapter = getAdapter();
+ if (adapter == null) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ while (SystemClock.uptimeMillis() <= endTime) {
+ rowId = adapter.getGroupId(seedGroupPosition);
+ if (rowId == groupIdToMatch) {
+ // Found it!
+ return seedGroupPosition;
+ }
+
+ hitLast = last == count - 1;
+ hitFirst = first == 0;
+
+ if (hitLast && hitFirst) {
+ // Looked at everything
+ break;
+ }
+
+ if (hitFirst || (next && !hitLast)) {
+ // Either we hit the top, or we are trying to move down
+ last++;
+ seedGroupPosition = last;
+ // Try going up next time
+ next = false;
+ } else if (hitLast || (!next && !hitFirst)) {
+ // Either we hit the bottom, or we are trying to move up
+ first--;
+ seedGroupPosition = first;
+ // Try going down next time
+ next = true;
+ }
+ }
+
+ return AdapterView.INVALID_POSITION;
+ }
+
+ protected class MyDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ refreshExpGroupMetadataList(true, true);
+
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ refreshExpGroupMetadataList(true, true);
+
+ notifyDataSetInvalidated();
+ }
+ }
+
+ /**
+ * Metadata about an expanded group to help convert from a flat list
+ * position to either a) group position for groups, or b) child position for
+ * children
+ */
+ static class GroupMetadata implements Parcelable, Comparable {
+ final static int REFRESH = -1;
+
+ /**
+ * This group's flat list position
+ */
+ int flPos;
+
+ /* firstChildFlPos isn't needed since it's (flPos + 1) */
+
+ /**
+ * This group's last child's flat list position, so basically
+ * the range of this group in the flat list
+ */
+ int lastChildFlPos;
+
+ /**
+ * This group's group position
+ */
+ int gPos;
+
+ /**
+ * This group's id
+ */
+ long gId;
+
+ private GroupMetadata() {
+ }
+
+ static GroupMetadata obtain(int flPos, int lastChildFlPos, int gPos, long gId) {
+ GroupMetadata gm = new GroupMetadata();
+ gm.flPos = flPos;
+ gm.lastChildFlPos = lastChildFlPos;
+ gm.gPos = gPos;
+ gm.gId = gId;
+ return gm;
+ }
+
+ public int compareTo(GroupMetadata another) {
+ if (another == null) {
+ throw new IllegalArgumentException();
+ }
+
+ return gPos - another.gPos;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(flPos);
+ dest.writeInt(lastChildFlPos);
+ dest.writeInt(gPos);
+ dest.writeLong(gId);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator CREATOR = null
+ /*new Parcelable.Creator() {
+ public GroupMetadata createFromParcel(Parcel in) {
+ GroupMetadata gm = GroupMetadata.obtain(
+ in.readInt(),
+ in.readInt(),
+ in.readInt(),
+ in.readLong());
+ return gm;
+ }
+
+ public GroupMetadata[] newArray(int size) {
+ return new GroupMetadata[size];
+ }
+ }*/;
+ }
+
+ /**
+ * Data type that contains an expandable list position (can refer to either a group
+ * or child) and some extra information regarding referred item (such as
+ * where to insert into the flat list, etc.)
+ */
+ static public class PositionMetadata {
+
+ private static final int MAX_POOL_SIZE = 5;
+ private static ArrayList sPool =
+ new ArrayList(MAX_POOL_SIZE);
+
+ /**
+ * Data type to hold the position and its type (child/group)
+ */
+ public ExpandableListPosition position;
+
+ /**
+ * Link back to the expanded GroupMetadata for this group. Useful for
+ * removing the group from the list of expanded groups inside the
+ * connector when we collapse the group, and also as a check to see if
+ * the group was expanded or collapsed (this will be null if the group
+ * is collapsed since we don't keep that group's metadata)
+ */
+ public GroupMetadata groupMetadata;
+
+ /**
+ * For groups that are collapsed, we use this as the index (in
+ * mExpGroupMetadataList) to insert this group when we are expanding
+ * this group.
+ */
+ public int groupInsertIndex;
+
+ private void resetState() {
+ if (position != null) {
+ position.recycle();
+ position = null;
+ }
+ groupMetadata = null;
+ groupInsertIndex = 0;
+ }
+
+ /**
+ * Use {@link #obtain(int, int, int, int, GroupMetadata, int)}
+ */
+ private PositionMetadata() {
+ }
+
+ static PositionMetadata obtain(int flatListPos, int type, int groupPos,
+ int childPos, GroupMetadata groupMetadata, int groupInsertIndex) {
+ PositionMetadata pm = getRecycledOrCreate();
+ pm.position = ExpandableListPosition.obtain(type, groupPos, childPos, flatListPos);
+ pm.groupMetadata = groupMetadata;
+ pm.groupInsertIndex = groupInsertIndex;
+ return pm;
+ }
+
+ private static PositionMetadata getRecycledOrCreate() {
+ PositionMetadata pm;
+ synchronized (sPool) {
+ if (sPool.size() > 0) {
+ pm = sPool.remove(0);
+ } else {
+ return new PositionMetadata();
+ }
+ }
+ pm.resetState();
+ return pm;
+ }
+
+ public void recycle() {
+ resetState();
+ synchronized (sPool) {
+ if (sPool.size() < MAX_POOL_SIZE) {
+ sPool.add(this);
+ }
+ }
+ }
+
+ /**
+ * Checks whether the group referred to in this object is expanded,
+ * or not (at the time this object was created)
+ *
+ * @return whether the group at groupPos is expanded or not
+ */
+ public boolean isExpanded() {
+ return groupMetadata != null;
+ }
+ }
+}
diff --git a/src/api-impl/android/widget/ExpandableListPosition.java b/src/api-impl/android/widget/ExpandableListPosition.java
new file mode 100644
index 00000000..3962346b
--- /dev/null
+++ b/src/api-impl/android/widget/ExpandableListPosition.java
@@ -0,0 +1,141 @@
+/*
+ * 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 java.util.ArrayList;
+
+/**
+ * ExpandableListPosition can refer to either a group's position or a child's
+ * position. Referring to a child's position requires both a group position (the
+ * group containing the child) and a child position (the child's position within
+ * that group). To create objects, use {@link #obtainChildPosition(int, int)} or
+ * {@link #obtainGroupPosition(int)}.
+ */
+class ExpandableListPosition {
+
+ private static final int MAX_POOL_SIZE = 5;
+ private static ArrayList sPool =
+ new ArrayList(MAX_POOL_SIZE);
+
+ /**
+ * This data type represents a child position
+ */
+ public final static int CHILD = 1;
+
+ /**
+ * This data type represents a group position
+ */
+ public final static int GROUP = 2;
+
+ /**
+ * The position of either the group being referred to, or the parent
+ * group of the child being referred to
+ */
+ public int groupPos;
+
+ /**
+ * The position of the child within its parent group
+ */
+ public int childPos;
+
+ /**
+ * The position of the item in the flat list (optional, used internally when
+ * the corresponding flat list position for the group or child is known)
+ */
+ int flatListPos;
+
+ /**
+ * What type of position this ExpandableListPosition represents
+ */
+ public int type;
+
+ private void resetState() {
+ groupPos = 0;
+ childPos = 0;
+ flatListPos = 0;
+ type = 0;
+ }
+
+ private ExpandableListPosition() {
+ }
+
+ long getPackedPosition() {
+ if (type == CHILD)
+ return ExpandableListView.getPackedPositionForChild(groupPos, childPos);
+ else
+ return ExpandableListView.getPackedPositionForGroup(groupPos);
+ }
+
+ static ExpandableListPosition obtainGroupPosition(int groupPosition) {
+ return obtain(GROUP, groupPosition, 0, 0);
+ }
+
+ static ExpandableListPosition obtainChildPosition(int groupPosition, int childPosition) {
+ return obtain(CHILD, groupPosition, childPosition, 0);
+ }
+
+ static ExpandableListPosition obtainPosition(long packedPosition) {
+ if (packedPosition == ExpandableListView.PACKED_POSITION_VALUE_NULL) {
+ return null;
+ }
+
+ ExpandableListPosition elp = getRecycledOrCreate();
+ elp.groupPos = ExpandableListView.getPackedPositionGroup(packedPosition);
+ if (ExpandableListView.getPackedPositionType(packedPosition) ==
+ ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ elp.type = CHILD;
+ elp.childPos = ExpandableListView.getPackedPositionChild(packedPosition);
+ } else {
+ elp.type = GROUP;
+ }
+ return elp;
+ }
+
+ static ExpandableListPosition obtain(int type, int groupPos, int childPos, int flatListPos) {
+ ExpandableListPosition elp = getRecycledOrCreate();
+ elp.type = type;
+ elp.groupPos = groupPos;
+ elp.childPos = childPos;
+ elp.flatListPos = flatListPos;
+ return elp;
+ }
+
+ private static ExpandableListPosition getRecycledOrCreate() {
+ ExpandableListPosition elp;
+ synchronized (sPool) {
+ if (sPool.size() > 0) {
+ elp = sPool.remove(0);
+ } else {
+ return new ExpandableListPosition();
+ }
+ }
+ elp.resetState();
+ return elp;
+ }
+
+ /**
+ * Do not call this unless you obtained this via ExpandableListPosition.obtain().
+ * PositionMetadata will handle recycling its own children.
+ */
+ public void recycle() {
+ synchronized (sPool) {
+ if (sPool.size() < MAX_POOL_SIZE) {
+ sPool.add(this);
+ }
+ }
+ }
+}
diff --git a/src/api-impl/android/widget/ExpandableListView.java b/src/api-impl/android/widget/ExpandableListView.java
index 3da05238..8ca493a2 100644
--- a/src/api-impl/android/widget/ExpandableListView.java
+++ b/src/api-impl/android/widget/ExpandableListView.java
@@ -1,26 +1,1435 @@
+/*
+ * 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.AttributeSet;
-import android.view.View;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+//import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+//import android.view.ContextMenu;
+//import android.view.ContextMenu.ContextMenuInfo;
+import android.view.SoundEffectConstants;
+import android.view.View;
+//import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ExpandableListConnector.PositionMetadata;
+import com.android.internal.R;
+import java.util.ArrayList;
+
+/**
+ * A view that shows items in a vertically scrolling two-level list. This
+ * differs from the {@link ListView} by allowing two levels: groups which can
+ * individually be expanded to show its children. The items come from the
+ * {@link ExpandableListAdapter} associated with this view.
+ *
+ * Expandable lists are able to show an indicator beside each item to display
+ * the item's current state (the states are usually one of expanded group,
+ * collapsed group, child, or last child). Use
+ * {@link #setChildIndicator(Drawable)} or {@link #setGroupIndicator(Drawable)}
+ * (or the corresponding XML attributes) to set these indicators (see the docs
+ * for each method to see additional state that each Drawable can have). The
+ * default style for an {@link ExpandableListView} provides indicators which
+ * will be shown next to Views given to the {@link ExpandableListView}. The
+ * layouts android.R.layout.simple_expandable_list_item_1 and
+ * android.R.layout.simple_expandable_list_item_2 (which should be used with
+ * {@link SimpleCursorTreeAdapter}) contain the preferred position information
+ * for indicators.
+ *
+ * The context menu information set by an {@link ExpandableListView} will be a
+ * {@link ExpandableListContextMenuInfo} object with
+ * {@link ExpandableListContextMenuInfo#packedPosition} being a packed position
+ * that can be used with {@link #getPackedPositionType(long)} and the other
+ * similar methods.
+ *
+ * Note: You cannot use the value wrap_content
+ * for the android:layout_height
attribute of a
+ * ExpandableListView in XML if the parent's size is also not strictly specified
+ * (for example, if the parent were ScrollView you could not specify
+ * wrap_content since it also can be any length. However, you can use
+ * wrap_content if the ExpandableListView parent has a specific size, such as
+ * 100 pixels.
+ *
+ * @attr ref android.R.styleable#ExpandableListView_groupIndicator
+ * @attr ref android.R.styleable#ExpandableListView_indicatorLeft
+ * @attr ref android.R.styleable#ExpandableListView_indicatorRight
+ * @attr ref android.R.styleable#ExpandableListView_childIndicator
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
+ * @attr ref android.R.styleable#ExpandableListView_childDivider
+ * @attr ref android.R.styleable#ExpandableListView_indicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_indicatorEnd
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorEnd
+ */
public class ExpandableListView extends ListView {
+ /**
+ * The packed position represents a group.
+ */
+ public static final int PACKED_POSITION_TYPE_GROUP = 0;
+
+ /**
+ * The packed position represents a child.
+ */
+ public static final int PACKED_POSITION_TYPE_CHILD = 1;
+
+ /**
+ * The packed position represents a neither/null/no preference.
+ */
+ public static final int PACKED_POSITION_TYPE_NULL = 2;
+
+ /**
+ * The value for a packed position that represents neither/null/no
+ * preference. This value is not otherwise possible since a group type
+ * (first bit 0) should not have a child position filled.
+ */
+ public static final long PACKED_POSITION_VALUE_NULL = 0x00000000FFFFFFFFL;
+
+ /**
+ * The mask (in packed position representation) for the child
+ */
+ private static final long PACKED_POSITION_MASK_CHILD = 0x00000000FFFFFFFFL;
+
+ /**
+ * The mask (in packed position representation) for the group
+ */
+ private static final long PACKED_POSITION_MASK_GROUP = 0x7FFFFFFF00000000L;
+
+ /**
+ * The mask (in packed position representation) for the type
+ */
+ private static final long PACKED_POSITION_MASK_TYPE = 0x8000000000000000L;
+
+ /**
+ * The shift amount (in packed position representation) for the group
+ */
+ private static final long PACKED_POSITION_SHIFT_GROUP = 32;
+
+ /**
+ * The shift amount (in packed position representation) for the type
+ */
+ private static final long PACKED_POSITION_SHIFT_TYPE = 63;
+
+ /**
+ * The mask (in integer child position representation) for the child
+ */
+ private static final long PACKED_POSITION_INT_MASK_CHILD = 0xFFFFFFFF;
+
+ /**
+ * The mask (in integer group position representation) for the group
+ */
+ private static final long PACKED_POSITION_INT_MASK_GROUP = 0x7FFFFFFF;
+
+ /**
+ * Serves as the glue/translator between a ListView and an ExpandableListView
+ */
+ //@UnsupportedAppUsage
+ private ExpandableListConnector mConnector;
+
+ /**
+ * Gives us Views through group+child positions
+ */
+ private ExpandableListAdapter mAdapter;
+
+ /**
+ * Left bound for drawing the indicator.
+ */
+ //@UnsupportedAppUsage
+ private int mIndicatorLeft;
+
+ /**
+ * Right bound for drawing the indicator.
+ */
+ //@UnsupportedAppUsage
+ private int mIndicatorRight;
+
+ /**
+ * Start bound for drawing the indicator.
+ */
+ private int mIndicatorStart;
+
+ /**
+ * End bound for drawing the indicator.
+ */
+ private int mIndicatorEnd;
+
+ /**
+ * Left bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
+ */
+ private int mChildIndicatorLeft;
+
+ /**
+ * Right bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorRight.
+ */
+ private int mChildIndicatorRight;
+
+ /**
+ * Start bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorStart.
+ */
+ private int mChildIndicatorStart;
+
+ /**
+ * End bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorEnd.
+ */
+ private int mChildIndicatorEnd;
+
+ /**
+ * Denotes when a child indicator should inherit this bound from the generic
+ * indicator bounds
+ */
+ public static final int CHILD_INDICATOR_INHERIT = -1;
+
+ /**
+ * Denotes an undefined value for an indicator
+ */
+ private static final int INDICATOR_UNDEFINED = -2;
+
+ /**
+ * The indicator drawn next to a group.
+ */
+ //@UnsupportedAppUsage
+ private Drawable mGroupIndicator;
+
+ /**
+ * The indicator drawn next to a child.
+ */
+ private Drawable mChildIndicator;
+
+ private static final int[] EMPTY_STATE_SET = {};
+
+ /**
+ * State indicating the group is expanded.
+ */
+ private static final int[] GROUP_EXPANDED_STATE_SET =
+ {R.attr.state_expanded};
+
+ /**
+ * State indicating the group is empty (has no children).
+ */
+ private static final int[] GROUP_EMPTY_STATE_SET =
+ {R.attr.state_empty};
+
+ /**
+ * State indicating the group is expanded and empty (has no children).
+ */
+ private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET =
+ {R.attr.state_expanded, R.attr.state_empty};
+
+ /**
+ * States for the group where the 0th bit is expanded and 1st bit is empty.
+ */
+ //@UnsupportedAppUsage
+ private static final int[][] GROUP_STATE_SETS = {
+ EMPTY_STATE_SET, // 00
+ GROUP_EXPANDED_STATE_SET, // 01
+ GROUP_EMPTY_STATE_SET, // 10
+ GROUP_EXPANDED_EMPTY_STATE_SET // 11
+ };
+
+ /**
+ * State indicating the child is the last within its group.
+ */
+ private static final int[] CHILD_LAST_STATE_SET =
+ {R.attr.state_last};
+
+ /**
+ * Drawable to be used as a divider when it is adjacent to any children
+ */
+ //@UnsupportedAppUsage
+ private Drawable mChildDivider;
+
+ // Bounds of the indicator to be drawn
+ private final Rect mIndicatorRect = new Rect();
+
public ExpandableListView(Context context) {
- super(context);
+ this(context, null);
}
- public ExpandableListView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
+ public ExpandableListView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.expandableListViewStyle);
}
+ public ExpandableListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ExpandableListView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExpandableListView, defStyleAttr, defStyleRes);
+ saveAttributeDataForStyleable(context, com.android.internal.R.styleable.ExpandableListView,
+ attrs, a, defStyleAttr, defStyleRes);
+
+ mGroupIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_groupIndicator);
+ mChildIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childIndicator);
+ mIndicatorLeft = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
+ mIndicatorRight = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
+ if (mIndicatorRight == 0 && mGroupIndicator != null) {
+ mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
+ }
+ mChildIndicatorLeft = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft,
+ CHILD_INDICATOR_INHERIT);
+ mChildIndicatorRight = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorRight,
+ CHILD_INDICATOR_INHERIT);
+ mChildDivider = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childDivider);
+
+ if (!isRtlCompatibilityMode()) {
+ mIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorStart,
+ INDICATOR_UNDEFINED);
+ mIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorEnd,
+ INDICATOR_UNDEFINED);
+
+ mChildIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorStart,
+ CHILD_INDICATOR_INHERIT);
+ mChildIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorEnd,
+ CHILD_INDICATOR_INHERIT);
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+ * RTL not supported)
+ */
+ private boolean isRtlCompatibilityMode() {
+ //final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ //return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport();
+ return true;
+ }
+
+ /**
+ * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
+ */
+ private boolean hasRtlSupport() {
+ //return mContext.getApplicationInfo().hasRtlSupport();
+ return false;
+ }
+
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ resolveIndicator();
+ resolveChildIndicator();
+ }
+
+ /**
+ * Resolve start/end indicator. start/end indicator always takes precedence over left/right
+ * indicator when defined.
+ */
+ private void resolveIndicator() {
+ final boolean isLayoutRtl = false/*isLayoutRtl()*/;
+ if (isLayoutRtl) {
+ if (mIndicatorStart >= 0) {
+ mIndicatorRight = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorLeft = mIndicatorEnd;
+ }
+ } else {
+ if (mIndicatorStart >= 0) {
+ mIndicatorLeft = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorRight = mIndicatorEnd;
+ }
+ }
+ if (mIndicatorRight == 0 && mGroupIndicator != null) {
+ mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
+ }
+ }
+
+ /**
+ * Resolve start/end child indicator. start/end child indicator always takes precedence over
+ * left/right child indicator when defined.
+ */
+ private void resolveChildIndicator() {
+ final boolean isLayoutRtl = false/*isLayoutRtl()*/;
+ if (isLayoutRtl) {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorEnd;
+ }
+ } else {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorEnd;
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ // Draw children, etc.
+ super.dispatchDraw(canvas);
+
+ // If we have any indicators to draw, we do it here
+ if ((mChildIndicator == null) && (mGroupIndicator == null)) {
+ return;
+ }
+
+ int saveCount = 0;
+ final boolean clipToPadding = false/*(mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK*/;
+ if (clipToPadding) {
+ saveCount = canvas.save();
+ final int scrollX = getScrollX();
+ final int scrollY = getScrollY();
+ canvas.clipRect(scrollX + getPaddingLeft(), scrollY + getPaddingTop(),
+ scrollX + getRight() - getLeft() - getPaddingRight(),
+ scrollY + getBottom() - getTop() - getPaddingBottom());
+ }
+
+ final int headerViewsCount = getHeaderViewsCount();
+
+ final int lastChildFlPos = mItemCount - getFooterViewsCount() - headerViewsCount - 1;
+
+ final int myB = getBottom();
+
+ PositionMetadata pos;
+ View item;
+ Drawable indicator;
+ int t, b;
+
+ // Start at a value that is neither child nor group
+ int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
+
+ final Rect indicatorRect = mIndicatorRect;
+
+ // The "child" mentioned in the following two lines is this
+ // View's child, not referring to an expandable list's
+ // notion of a child (as opposed to a group)
+ final int childCount = getChildCount();
+ for (int i = 0, childFlPos = mFirstPosition - headerViewsCount; i < childCount;
+ i++, childFlPos++) {
+
+ if (childFlPos < 0) {
+ // This child is header
+ continue;
+ } else if (childFlPos > lastChildFlPos) {
+ // This child is footer, so are all subsequent children
+ break;
+ }
+
+ item = getChildAt(i);
+ t = item.getTop();
+ b = item.getBottom();
+
+ // This item isn't on the screen
+ if ((b < 0) || (t > myB))
+ continue;
+
+ // Get more expandable list-related info for this item
+ pos = mConnector.getUnflattenedPos(childFlPos);
+
+ final boolean isLayoutRtl = false/*isLayoutRtl()*/;
+ final int width = getWidth();
+
+ // If this item type and the previous item type are different, then we need to change
+ // the left & right bounds
+ if (pos.position.type != lastItemType) {
+ if (pos.position.type == ExpandableListPosition.CHILD) {
+ indicatorRect.left = (mChildIndicatorLeft == CHILD_INDICATOR_INHERIT) ? mIndicatorLeft : mChildIndicatorLeft;
+ indicatorRect.right = (mChildIndicatorRight == CHILD_INDICATOR_INHERIT) ? mIndicatorRight : mChildIndicatorRight;
+ } else {
+ indicatorRect.left = mIndicatorLeft;
+ indicatorRect.right = mIndicatorRight;
+ }
+
+ if (isLayoutRtl) {
+ final int temp = indicatorRect.left;
+ indicatorRect.left = width - indicatorRect.right;
+ indicatorRect.right = width - temp;
+
+ indicatorRect.left -= getPaddingRight();
+ indicatorRect.right -= getPaddingRight();
+ } else {
+ indicatorRect.left += getPaddingLeft();
+ indicatorRect.right += getPaddingLeft();
+ }
+
+ lastItemType = pos.position.type;
+ }
+
+ if (indicatorRect.left != indicatorRect.right) {
+ // Use item's full height + the divider height
+ if (mStackFromBottom) {
+ // See ListView#dispatchDraw
+ indicatorRect.top = t; // - mDividerHeight;
+ indicatorRect.bottom = b;
+ } else {
+ indicatorRect.top = t;
+ indicatorRect.bottom = b; // + mDividerHeight;
+ }
+
+ // Get the indicator (with its state set to the item's state)
+ indicator = getIndicator(pos);
+ if (indicator != null) {
+ // Draw the indicator
+ indicator.setBounds(indicatorRect);
+ indicator.draw(canvas);
+ }
+ }
+ pos.recycle();
+ }
+
+ if (clipToPadding) {
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ /**
+ * Gets the indicator for the item at the given position. If the indicator
+ * is stateful, the state will be given to the indicator.
+ *
+ * @param pos The flat list position of the item whose indicator
+ * should be returned.
+ * @return The indicator in the proper state.
+ */
+ private Drawable getIndicator(PositionMetadata pos) {
+ Drawable indicator;
+
+ if (pos.position.type == ExpandableListPosition.GROUP) {
+ indicator = mGroupIndicator;
+
+ if (indicator != null && indicator.isStateful()) {
+ // Empty check based on availability of data. If the groupMetadata isn't null,
+ // we do a check on it. Otherwise, the group is collapsed so we consider it
+ // empty for performance reasons.
+ boolean isEmpty = (pos.groupMetadata == null) ||
+ (pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
+
+ final int stateSetIndex =
+ (pos.isExpanded() ? 1 : 0) | // Expanded?
+ (isEmpty ? 2 : 0); // Empty?
+ indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
+ }
+ } else {
+ indicator = mChildIndicator;
+
+ if (indicator != null && indicator.isStateful()) {
+ // No need for a state sets array for the child since it only has two states
+ final int stateSet[] = pos.position.flatListPos == pos.groupMetadata.lastChildFlPos
+ ? CHILD_LAST_STATE_SET
+ : EMPTY_STATE_SET;
+ indicator.setState(stateSet);
+ }
+ }
+
+ return indicator;
+ }
+
+ /**
+ * Sets the drawable that will be drawn adjacent to every child in the list. This will
+ * be drawn using the same height as the normal divider ({@link #setDivider(Drawable)}) or
+ * if it does not have an intrinsic height, the height set by {@link #setDividerHeight(int)}.
+ *
+ * @param childDivider The drawable to use.
+ */
+ public void setChildDivider(Drawable childDivider) {
+ mChildDivider = childDivider;
+ }
+
+ @Override
+ void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
+ int flatListPosition = childIndex + mFirstPosition;
+
+ // Only proceed as possible child if the divider isn't above all items (if it is above
+ // all items, then the item below it has to be a group)
+ if (flatListPosition >= 0) {
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+ PositionMetadata pos = mConnector.getUnflattenedPos(adjustedPosition);
+ // If this item is a child, or it is a non-empty group that is expanded
+ if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
+ pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
+ // These are the cases where we draw the child divider
+ final Drawable divider = mChildDivider;
+ divider.setBounds(bounds);
+ divider.draw(canvas);
+ pos.recycle();
+ return;
+ }
+ pos.recycle();
+ }
+
+ // Otherwise draw the default divider
+ super.drawDivider(canvas, bounds, flatListPosition);
+ }
+
+ /**
+ * This overloaded method should not be used, instead use
+ * {@link #setAdapter(ExpandableListAdapter)}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ throw new RuntimeException(
+ "For ExpandableListView, use setAdapter(ExpandableListAdapter) instead of "
+ +
+ "setAdapter(ListAdapter)");
+ }
+
+ /**
+ * This method should not be used, use {@link #getExpandableListAdapter()}.
+ */
+ @Override
+ public ListAdapter getAdapter() {
+ /*
+ * The developer should never really call this method on an
+ * ExpandableListView, so it would be nice to throw a RuntimeException,
+ * but AdapterView calls this
+ */
+ return super.getAdapter();
+ }
+
+ /**
+ * Register a callback to be invoked when an item has been clicked and the
+ * caller prefers to receive a ListView-style position instead of a group
+ * and/or child position. In most cases, the caller should use
+ * {@link #setOnGroupClickListener} and/or {@link #setOnChildClickListener}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void setOnItemClickListener(OnItemClickListener l) {
+ super.setOnItemClickListener(l);
+ }
+
+ /**
+ * Sets the adapter that provides data to this view.
+ * @param adapter The adapter that provides data to this view.
+ */
+ public void setAdapter(ExpandableListAdapter adapter) {
+ // Set member variable
+ mAdapter = adapter;
+
+ if (adapter != null) {
+ // Create the connector
+ mConnector = new ExpandableListConnector(adapter);
+ } else {
+ mConnector = null;
+ }
+
+ // Link the ListView (superclass) to the expandable list data through the connector
+ super.setAdapter(mConnector);
+ }
+
+ /**
+ * Gets the adapter that provides data to this view.
+ * @return The adapter that provides data to this view.
+ */
+ public ExpandableListAdapter getExpandableListAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * @param position An absolute (including header and footer) flat list position.
+ * @return true if the position corresponds to a header or a footer item.
+ */
+ private boolean isHeaderOrFooterPosition(int position) {
+ final int footerViewsStart = mItemCount - getFooterViewsCount();
+ return (position < getHeaderViewsCount() || position >= footerViewsStart);
+ }
+
+ /**
+ * Converts an absolute item flat position into a group/child flat position, shifting according
+ * to the number of header items.
+ *
+ * @param flatListPosition The absolute flat position
+ * @return A group/child flat position as expected by the connector.
+ */
+ private int getFlatPositionForConnector(int flatListPosition) {
+ return flatListPosition - getHeaderViewsCount();
+ }
+
+ /**
+ * Converts a group/child flat position into an absolute flat position, that takes into account
+ * the possible headers.
+ *
+ * @param flatListPosition The child/group flat position
+ * @return An absolute flat position.
+ */
+ private int getAbsoluteFlatPosition(int flatListPosition) {
+ return flatListPosition + getHeaderViewsCount();
+ }
+
+ @Override
+ public boolean performItemClick(View v, int position, long id) {
+ // Ignore clicks in header/footers
+ if (isHeaderOrFooterPosition(position)) {
+ // Clicked on a header/footer, so ignore pass it on to super
+ return super.performItemClick(v, position, id);
+ }
+
+ // Internally handle the item click
+ final int adjustedPosition = getFlatPositionForConnector(position);
+ final boolean clicked = handleItemClick(v, adjustedPosition, id);
+ if (v != null) {
+ //v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+ return clicked;
+ }
+
+ /**
+ * This will either expand/collapse groups (if a group was clicked) or pass
+ * on the click to the proper child (if a child was clicked)
+ *
+ * @param position The flat list position. This has already been factored to
+ * remove the header/footer.
+ * @param id The ListAdapter ID, not the group or child ID.
+ */
+ boolean handleItemClick(View v, int position, long id) {
+ final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);
+
+ id = getChildOrGroupId(posMetadata.position);
+
+ boolean returnValue;
+ if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+ /* It's a group, so handle collapsing/expanding */
+
+ /* It's a group click, so pass on event */
+ if (mOnGroupClickListener != null) {
+ if (mOnGroupClickListener.onGroupClick(this, v,
+ posMetadata.position.groupPos, id)) {
+ posMetadata.recycle();
+ return true;
+ }
+ }
+
+ if (posMetadata.isExpanded()) {
+ /* Collapse it */
+ mConnector.collapseGroup(posMetadata);
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+
+ if (mOnGroupCollapseListener != null) {
+ mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
+ }
+ } else {
+ /* Expand it */
+ mConnector.expandGroup(posMetadata);
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+
+ if (mOnGroupExpandListener != null) {
+ mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
+ }
+
+ final int groupPos = posMetadata.position.groupPos;
+ final int groupFlatPos = posMetadata.position.flatListPos;
+
+ final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
+ smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
+ shiftedGroupPosition);
+ }
+
+ returnValue = true;
+ } else {
+ /* It's a child, so pass on event */
+ if (mOnChildClickListener != null) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
+ posMetadata.position.childPos, id);
+ }
+
+ returnValue = false;
+ }
+
+ posMetadata.recycle();
+
+ return returnValue;
+ }
+
+ /**
+ * Expand a group in the grouped list view
+ *
+ * @param groupPos the group to be expanded
+ * @return True if the group was expanded, false otherwise (if the group
+ * was already expanded, this will return false)
+ */
+ public boolean expandGroup(int groupPos) {
+ return expandGroup(groupPos, false);
+ }
+
+ /**
+ * Expand a group in the grouped list view
+ *
+ * @param groupPos the group to be expanded
+ * @param animate true if the expanding group should be animated in
+ * @return True if the group was expanded, false otherwise (if the group
+ * was already expanded, this will return false)
+ */
+ public boolean expandGroup(int groupPos, boolean animate) {
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
+ boolean retValue = mConnector.expandGroup(pm);
+
+ if (mOnGroupExpandListener != null) {
+ mOnGroupExpandListener.onGroupExpand(groupPos);
+ }
+
+ if (animate) {
+ final int groupFlatPos = pm.position.flatListPos;
+
+ final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
+ smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
+ shiftedGroupPosition);
+ }
+ pm.recycle();
+
+ return retValue;
+ }
+
+ /**
+ * Collapse a group in the grouped list view
+ *
+ * @param groupPos position of the group to collapse
+ * @return True if the group was collapsed, false otherwise (if the group
+ * was already collapsed, this will return false)
+ */
+ public boolean collapseGroup(int groupPos) {
+ boolean retValue = mConnector.collapseGroup(groupPos);
+
+ if (mOnGroupCollapseListener != null) {
+ mOnGroupCollapseListener.onGroupCollapse(groupPos);
+ }
+
+ return retValue;
+ }
+
+ /**
+ * Used for being notified when a group is collapsed
+ */
+ public interface OnGroupCollapseListener {
+ /**
+ * Callback method to be invoked when a group in this expandable list has
+ * been collapsed.
+ *
+ * @param groupPosition The group position that was collapsed
+ */
+ void onGroupCollapse(int groupPosition);
+ }
+
+ //@UnsupportedAppUsage
+ private OnGroupCollapseListener mOnGroupCollapseListener;
+
+ public void setOnGroupCollapseListener(
+ OnGroupCollapseListener onGroupCollapseListener) {
+ mOnGroupCollapseListener = onGroupCollapseListener;
+ }
+
+ /**
+ * Used for being notified when a group is expanded
+ */
+ public interface OnGroupExpandListener {
+ /**
+ * Callback method to be invoked when a group in this expandable list has
+ * been expanded.
+ *
+ * @param groupPosition The group position that was expanded
+ */
+ void onGroupExpand(int groupPosition);
+ }
+
+ //@UnsupportedAppUsage
+ private OnGroupExpandListener mOnGroupExpandListener;
+
+ public void setOnGroupExpandListener(
+ OnGroupExpandListener onGroupExpandListener) {
+ mOnGroupExpandListener = onGroupExpandListener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a group in this
+ * expandable list has been clicked.
+ */
+ public interface OnGroupClickListener {
+ /**
+ * Callback method to be invoked when a group in this expandable list has
+ * been clicked.
+ *
+ * @param parent The ExpandableListConnector where the click happened
+ * @param v The view within the expandable list/ListView that was clicked
+ * @param groupPosition The group position that was clicked
+ * @param id The row id of the group that was clicked
+ * @return True if the click was handled
+ */
+ boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
+ long id);
+ }
+
+ //@UnsupportedAppUsage
+ private OnGroupClickListener mOnGroupClickListener;
+
+ public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
+ mOnGroupClickListener = onGroupClickListener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a child in this
+ * expandable list has been clicked.
+ */
+ public interface OnChildClickListener {
+ /**
+ * Callback method to be invoked when a child in this expandable list has
+ * been clicked.
+ *
+ * @param parent The ExpandableListView where the click happened
+ * @param v The view within the expandable list/ListView that was clicked
+ * @param groupPosition The group position that contains the child that
+ * was clicked
+ * @param childPosition The child position within the group
+ * @param id The row id of the child that was clicked
+ * @return True if the click was handled
+ */
+ boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
+ int childPosition, long id);
+ }
+
+ //@UnsupportedAppUsage
+ private OnChildClickListener mOnChildClickListener;
+
+ public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
+ mOnChildClickListener = onChildClickListener;
+ }
+
+ /**
+ * Converts a flat list position (the raw position of an item (child or group)
+ * in the list) to a group and/or child position (represented in a
+ * packed position). This is useful in situations where the caller needs to
+ * use the underlying {@link ListView}'s methods. Use
+ * {@link ExpandableListView#getPackedPositionType} ,
+ * {@link ExpandableListView#getPackedPositionChild},
+ * {@link ExpandableListView#getPackedPositionGroup} to unpack.
+ *
+ * @param flatListPosition The flat list position to be converted.
+ * @return The group and/or child position for the given flat list position
+ * in packed position representation. #PACKED_POSITION_VALUE_NULL if
+ * the position corresponds to a header or a footer item.
+ */
+ public long getExpandableListPosition(int flatListPosition) {
+ if (isHeaderOrFooterPosition(flatListPosition)) {
+ return PACKED_POSITION_VALUE_NULL;
+ }
+
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+ PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
+ long packedPos = pm.position.getPackedPosition();
+ pm.recycle();
+ return packedPos;
+ }
+
+ /**
+ * Converts a group and/or child position to a flat list position. This is
+ * useful in situations where the caller needs to use the underlying
+ * {@link ListView}'s methods.
+ *
+ * @param packedPosition The group and/or child positions to be converted in
+ * packed position representation. Use
+ * {@link #getPackedPositionForChild(int, int)} or
+ * {@link #getPackedPositionForGroup(int)}.
+ * @return The flat list position for the given child or group.
+ */
+ public int getFlatListPosition(long packedPosition) {
+ ExpandableListPosition elPackedPos = ExpandableListPosition
+ .obtainPosition(packedPosition);
+ PositionMetadata pm = mConnector.getFlattenedPos(elPackedPos);
+ elPackedPos.recycle();
+ final int flatListPosition = pm.position.flatListPos;
+ pm.recycle();
+ return getAbsoluteFlatPosition(flatListPosition);
+ }
+
+ /**
+ * Gets the position of the currently selected group or child (along with
+ * its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
+ *
+ * @return A packed position containing the currently selected group or
+ * child's position and type. #PACKED_POSITION_VALUE_NULL if no selection
+ * or if selection is on a header or a footer item.
+ */
+ public long getSelectedPosition() {
+ final int selectedPos = getSelectedItemPosition();
+
+ // The case where there is no selection (selectedPos == -1) is also handled here.
+ return getExpandableListPosition(selectedPos);
+ }
+
+ /**
+ * Gets the ID of the currently selected group or child. Can return -1 if no
+ * selection.
+ *
+ * @return The ID of the currently selected group or child. -1 if no
+ * selection.
+ */
+ public long getSelectedId() {
+ long packedPos = getSelectedPosition();
+ if (packedPos == PACKED_POSITION_VALUE_NULL)
+ return -1;
+
+ int groupPos = getPackedPositionGroup(packedPos);
+
+ if (getPackedPositionType(packedPos) == PACKED_POSITION_TYPE_GROUP) {
+ // It's a group
+ return mAdapter.getGroupId(groupPos);
+ } else {
+ // It's a child
+ return mAdapter.getChildId(groupPos, getPackedPositionChild(packedPos));
+ }
+ }
+
+ /**
+ * Sets the selection to the specified group.
+ * @param groupPosition The position of the group that should be selected.
+ */
+ public void setSelectedGroup(int groupPosition) {
+ ExpandableListPosition elGroupPos = ExpandableListPosition
+ .obtainGroupPosition(groupPosition);
+ PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
+ final int absoluteFlatPosition = getAbsoluteFlatPosition(pm.position.flatListPos);
+ super.setSelection(absoluteFlatPosition);
+ pm.recycle();
+ }
+
+ /**
+ * Sets the selection to the specified child. If the child is in a collapsed
+ * group, the group will only be expanded and child subsequently selected if
+ * shouldExpandGroup is set to true, otherwise the method will return false.
+ *
+ * @param groupPosition The position of the group that contains the child.
+ * @param childPosition The position of the child within the group.
+ * @param shouldExpandGroup Whether the child's group should be expanded if
+ * it is collapsed.
+ * @return Whether the selection was successfully set on the child.
+ */
+ public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
+ ExpandableListPosition elChildPos = ExpandableListPosition.obtainChildPosition(
+ groupPosition, childPosition);
+ PositionMetadata flatChildPos = mConnector.getFlattenedPos(elChildPos);
+
+ if (flatChildPos == null) {
+ // The child's group isn't expanded
+
+ // Shouldn't expand the group, so return false for we didn't set the selection
+ if (!shouldExpandGroup)
+ return false;
+
+ expandGroup(groupPosition);
+
+ flatChildPos = mConnector.getFlattenedPos(elChildPos);
+
+ // Validity check
+ if (flatChildPos == null) {
+ throw new IllegalStateException("Could not find child");
+ }
+ }
+
+ int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
+ super.setSelection(absoluteFlatPosition);
+
+ elChildPos.recycle();
+ flatChildPos.recycle();
+
+ return true;
+ }
+
+ /**
+ * Whether the given group is currently expanded.
+ *
+ * @param groupPosition The group to check.
+ * @return Whether the group is currently expanded.
+ */
+ public boolean isGroupExpanded(int groupPosition) {
+ return mConnector.isGroupExpanded(groupPosition);
+ }
+
+ /**
+ * Gets the type of a packed position. See
+ * {@link #getPackedPositionForChild(int, int)}.
+ *
+ * @param packedPosition The packed position for which to return the type.
+ * @return The type of the position contained within the packed position,
+ * either {@link #PACKED_POSITION_TYPE_CHILD}, {@link #PACKED_POSITION_TYPE_GROUP}, or
+ * {@link #PACKED_POSITION_TYPE_NULL}.
+ */
+ public static int getPackedPositionType(long packedPosition) {
+ if (packedPosition == PACKED_POSITION_VALUE_NULL) {
+ return PACKED_POSITION_TYPE_NULL;
+ }
+
+ return (packedPosition & PACKED_POSITION_MASK_TYPE) == PACKED_POSITION_MASK_TYPE
+ ? PACKED_POSITION_TYPE_CHILD
+ : PACKED_POSITION_TYPE_GROUP;
+ }
+
+ /**
+ * Gets the group position from a packed position. See
+ * {@link #getPackedPositionForChild(int, int)}.
+ *
+ * @param packedPosition The packed position from which the group position
+ * will be returned.
+ * @return The group position portion of the packed position. If this does
+ * not contain a group, returns -1.
+ */
public static int getPackedPositionGroup(long packedPosition) {
- return 0;
+ // Null
+ if (packedPosition == PACKED_POSITION_VALUE_NULL)
+ return -1;
+
+ return (int)((packedPosition & PACKED_POSITION_MASK_GROUP) >> PACKED_POSITION_SHIFT_GROUP);
}
- public void setHeaderDividersEnabled(boolean enabled) {}
+ /**
+ * Gets the child position from a packed position that is of
+ * {@link #PACKED_POSITION_TYPE_CHILD} type (use {@link #getPackedPositionType(long)}).
+ * To get the group that this child belongs to, use
+ * {@link #getPackedPositionGroup(long)}. See
+ * {@link #getPackedPositionForChild(int, int)}.
+ *
+ * @param packedPosition The packed position from which the child position
+ * will be returned.
+ * @return The child position portion of the packed position. If this does
+ * not contain a child, returns -1.
+ */
+ public static int getPackedPositionChild(long packedPosition) {
+ // Null
+ if (packedPosition == PACKED_POSITION_VALUE_NULL)
+ return -1;
- public static interface OnChildClickListener {
- abstract boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id);
+ // Group since a group type clears this bit
+ if ((packedPosition & PACKED_POSITION_MASK_TYPE) != PACKED_POSITION_MASK_TYPE)
+ return -1;
+
+ return (int)(packedPosition & PACKED_POSITION_MASK_CHILD);
+ }
+
+ /**
+ * Returns the packed position representation of a child's position.
+ *
+ * In general, a packed position should be used in
+ * situations where the position given to/returned from an
+ * {@link ExpandableListAdapter} or {@link ExpandableListView} method can
+ * either be a child or group. The two positions are packed into a single
+ * long which can be unpacked using
+ * {@link #getPackedPositionChild(long)},
+ * {@link #getPackedPositionGroup(long)}, and
+ * {@link #getPackedPositionType(long)}.
+ *
+ * @param groupPosition The child's parent group's position.
+ * @param childPosition The child position within the group.
+ * @return The packed position representation of the child (and parent group).
+ */
+ public static long getPackedPositionForChild(int groupPosition, int childPosition) {
+ return (((long)PACKED_POSITION_TYPE_CHILD) << PACKED_POSITION_SHIFT_TYPE) | ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP) << PACKED_POSITION_SHIFT_GROUP) | (childPosition & PACKED_POSITION_INT_MASK_CHILD);
+ }
+
+ /**
+ * Returns the packed position representation of a group's position. See
+ * {@link #getPackedPositionForChild(int, int)}.
+ *
+ * @param groupPosition The child's parent group's position.
+ * @return The packed position representation of the group.
+ */
+ public static long getPackedPositionForGroup(int groupPosition) {
+ // No need to OR a type in because PACKED_POSITION_GROUP == 0
+ return ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
+ << PACKED_POSITION_SHIFT_GROUP);
+ }
+
+ /*@Override
+ ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
+ if (isHeaderOrFooterPosition(flatListPosition)) {
+ // Return normal info for header/footer view context menus
+ return new AdapterContextMenuInfo(view, flatListPosition, id);
+ }
+
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+ PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
+ ExpandableListPosition pos = pm.position;
+
+ id = getChildOrGroupId(pos);
+ long packedPosition = pos.getPackedPosition();
+
+ pm.recycle();
+
+ return new ExpandableListContextMenuInfo(view, packedPosition, id);
+ }*/
+
+ /**
+ * @hide
+ */
+ /*@Override
+ public void onInitializeAccessibilityNodeInfoForItem(
+ View view, int position, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
+ final PositionMetadata metadata = mConnector.getUnflattenedPos(position);
+ if (metadata.position.type == ExpandableListPosition.GROUP) {
+ if (view != null && view.isEnabled()) {
+ info.setClickable(true);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+ if (isGroupExpanded(metadata.position.groupPos)) {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+ } else {
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+ }
+ }
+ }
+
+ metadata.recycle();
+ }*/
+
+ /**
+ * Gets the ID of the group or child at the given position
.
+ * This is useful since there is no ListAdapter ID -> ExpandableListAdapter
+ * ID conversion mechanism (in some cases, it isn't possible).
+ *
+ * @param position The position of the child or group whose ID should be
+ * returned.
+ */
+ private long getChildOrGroupId(ExpandableListPosition position) {
+ if (position.type == ExpandableListPosition.CHILD) {
+ return mAdapter.getChildId(position.groupPos, position.childPos);
+ } else {
+ return mAdapter.getGroupId(position.groupPos);
+ }
+ }
+
+ /**
+ * Sets the indicator to be drawn next to a child.
+ *
+ * @param childIndicator The drawable to be used as an indicator. If the
+ * child is the last child for a group, the state
+ * {@link android.R.attr#state_last} will be set.
+ */
+ public void setChildIndicator(Drawable childIndicator) {
+ mChildIndicator = childIndicator;
+ }
+
+ /**
+ * Sets the drawing bounds for the child indicator. For either, you can
+ * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
+ * indicator's bounds.
+ *
+ * @see #setIndicatorBounds(int, int)
+ * @param left The left position (relative to the left bounds of this View)
+ * to start drawing the indicator.
+ * @param right The right position (relative to the left bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setChildIndicatorBounds(int left, int right) {
+ mChildIndicatorLeft = left;
+ mChildIndicatorRight = right;
+ resolveChildIndicator();
+ }
+
+ /**
+ * Sets the relative drawing bounds for the child indicator. For either, you can
+ * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
+ * indicator's bounds.
+ *
+ * @see #setIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setChildIndicatorBoundsRelative(int start, int end) {
+ mChildIndicatorStart = start;
+ mChildIndicatorEnd = end;
+ resolveChildIndicator();
+ }
+
+ /**
+ * Sets the indicator to be drawn next to a group.
+ *
+ * @param groupIndicator The drawable to be used as an indicator. If the
+ * group is empty, the state {@link android.R.attr#state_empty} will be
+ * set. If the group is expanded, the state
+ * {@link android.R.attr#state_expanded} will be set.
+ */
+ public void setGroupIndicator(Drawable groupIndicator) {
+ mGroupIndicator = groupIndicator;
+ if (mIndicatorRight == 0 && mGroupIndicator != null) {
+ mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
+ }
+ }
+
+ /**
+ * Sets the drawing bounds for the indicators (at minimum, the group indicator
+ * is affected by this; the child indicator is affected by this if the
+ * child indicator bounds are set to inherit).
+ *
+ * @see #setChildIndicatorBounds(int, int)
+ * @param left The left position (relative to the left bounds of this View)
+ * to start drawing the indicator.
+ * @param right The right position (relative to the left bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setIndicatorBounds(int left, int right) {
+ mIndicatorLeft = left;
+ mIndicatorRight = right;
+ resolveIndicator();
+ }
+
+ /**
+ * Sets the relative drawing bounds for the indicators (at minimum, the group indicator
+ * is affected by this; the child indicator is affected by this if the
+ * child indicator bounds are set to inherit).
+ *
+ * @see #setChildIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setIndicatorBoundsRelative(int start, int end) {
+ mIndicatorStart = start;
+ mIndicatorEnd = end;
+ resolveIndicator();
+ }
+
+ /**
+ * Extra menu information specific to an {@link ExpandableListView} provided
+ * to the
+ * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
+ * callback when a context menu is brought up for this AdapterView.
+ */
+ /*public static class ExpandableListContextMenuInfo implements ContextMenu.ContextMenuInfo {
+
+ public ExpandableListContextMenuInfo(View targetView, long packedPosition, long id) {
+ this.targetView = targetView;
+ this.packedPosition = packedPosition;
+ this.id = id;
+ }
+
+ /**
+ * The view for which the context menu is being displayed. This
+ * will be one of the children Views of this {@link ExpandableListView}.
+ * /
+ public View targetView;
+
+ /**
+ * The packed position in the list represented by the adapter for which
+ * the context menu is being displayed. Use the methods
+ * {@link ExpandableListView#getPackedPositionType},
+ * {@link ExpandableListView#getPackedPositionChild}, and
+ * {@link ExpandableListView#getPackedPositionGroup} to unpack this.
+ * /
+ public long packedPosition;
+
+ /**
+ * The ID of the item (group or child) for which the context menu is
+ * being displayed.
+ * /
+ public long id;
+ }*/
+
+ /*static class SavedState extends BaseSavedState {
+ ArrayList expandedGroupMetadataList;
+
+ /**
+ * Constructor called from {@link ExpandableListView#onSaveInstanceState()}
+ * /
+ SavedState(
+ Parcelable superState,
+ ArrayList expandedGroupMetadataList) {
+ super(superState);
+ this.expandedGroupMetadataList = expandedGroupMetadataList;
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ * /
+ private SavedState(Parcel in) {
+ super(in);
+ expandedGroupMetadataList = new ArrayList();
+ in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader(), android.widget.ExpandableListConnector.GroupMetadata.class);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeList(expandedGroupMetadataList);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ return new SavedState(superState,
+ mConnector != null ? mConnector.getExpandedGroupMetadataList() : null);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState ss = (SavedState)state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (mConnector != null && ss.expandedGroupMetadataList != null) {
+ mConnector.setExpandedGroupMetadataList(ss.expandedGroupMetadataList);
+ }
+ }*/
+
+ //@Override
+ public CharSequence getAccessibilityClassName() {
+ return ExpandableListView.class.getName();
}
}
diff --git a/src/api-impl/android/widget/HeterogeneousExpandableList.java b/src/api-impl/android/widget/HeterogeneousExpandableList.java
new file mode 100644
index 00000000..47d17e22
--- /dev/null
+++ b/src/api-impl/android/widget/HeterogeneousExpandableList.java
@@ -0,0 +1,106 @@
+/*
+ * 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.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Additional methods that when implemented make an
+ * {@link ExpandableListAdapter} take advantage of the {@link Adapter} view type
+ * mechanism.
+ *
+ * An {@link ExpandableListAdapter} declares it has one view type for its group items
+ * and one view type for its child items. Although adapted for most {@link ExpandableListView}s,
+ * these values should be tuned for heterogeneous {@link ExpandableListView}s.
+ *
+ * Lists that contain different types of group and/or child item views, should use an adapter that
+ * implements this interface. This way, the recycled views that will be provided to
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * and
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * will be of the appropriate group or child type, resulting in a more efficient reuse of the
+ * previously created views.
+ */
+public interface HeterogeneousExpandableList {
+ /**
+ * Get the type of group View that will be created by
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . for the specified group item.
+ *
+ * @param groupPosition the position of the group for which the type should be returned.
+ * @return An integer representing the type of group View. Two group views should share the same
+ * type if one can be converted to the other in
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . Note: Integers must be in the range 0 to {@link #getGroupTypeCount} - 1.
+ * {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+ * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+ * @see #getGroupTypeCount()
+ */
+ int getGroupType(int groupPosition);
+
+ /**
+ * Get the type of child View that will be created by
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * for the specified child item.
+ *
+ * @param groupPosition the position of the group that the child resides in
+ * @param childPosition the position of the child with respect to other children in the group
+ * @return An integer representing the type of child View. Two child views should share the same
+ * type if one can be converted to the other in
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * Note: Integers must be in the range 0 to {@link #getChildTypeCount} - 1.
+ * {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+ * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+ * @see #getChildTypeCount()
+ */
+ int getChildType(int groupPosition, int childPosition);
+
+ /**
+ *
+ * Returns the number of types of group Views that will be created by
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . Each type represents a set of views that can be converted in
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . If the adapter always returns the same type of View for all group items, this method should
+ * return 1.
+ *
+ * This method will only be called when the adapter is set on the {@link AdapterView}.
+ *
+ * @return The number of types of group Views that will be created by this adapter.
+ * @see #getChildTypeCount()
+ * @see #getGroupType(int)
+ */
+ int getGroupTypeCount();
+
+ /**
+ *
+ * Returns the number of types of child Views that will be created by
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * . Each type represents a set of views that can be converted in
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * , for any group. If the adapter always returns the same type of View for
+ * all child items, this method should return 1.
+ *
+ * This method will only be called when the adapter is set on the {@link AdapterView}.
+ *
+ * @return The total number of types of child Views that will be created by this adapter.
+ * @see #getGroupTypeCount()
+ * @see #getChildType(int, int)
+ */
+ int getChildTypeCount();
+}
diff --git a/src/api-impl/meson.build b/src/api-impl/meson.build
index 2f8a76f7..2afa31db 100644
--- a/src/api-impl/meson.build
+++ b/src/api-impl/meson.build
@@ -549,6 +549,7 @@ srcs = [
'android/widget/ArrayAdapter.java',
'android/widget/AutoCompleteTextView.java',
'android/widget/BaseAdapter.java',
+ 'android/widget/BaseExpandableListAdapter.java',
'android/widget/Button.java',
'android/widget/CheckBox.java',
'android/widget/Checkable.java',
@@ -557,6 +558,9 @@ srcs = [
'android/widget/CursorAdapter.java',
'android/widget/EdgeEffect.java',
'android/widget/EditText.java',
+ 'android/widget/ExpandableListAdapter.java',
+ 'android/widget/ExpandableListConnector.java',
+ 'android/widget/ExpandableListPosition.java',
'android/widget/ExpandableListView.java',
'android/widget/Filter.java',
'android/widget/Filterable.java',
@@ -565,6 +569,7 @@ srcs = [
'android/widget/Gallery.java',
'android/widget/GridView.java',
'android/widget/HeaderViewListAdapter.java',
+ 'android/widget/HeterogeneousExpandableList.java',
'android/widget/HorizontalScrollView.java',
'android/widget/ImageButton.java',
'android/widget/ImageView.java',