Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Rect;
     25 import android.graphics.drawable.Drawable;
     26 import android.os.Parcel;
     27 import android.os.Parcelable;
     28 import android.util.AttributeSet;
     29 import android.view.ContextMenu;
     30 import android.view.SoundEffectConstants;
     31 import android.view.View;
     32 import android.view.ContextMenu.ContextMenuInfo;
     33 import android.view.accessibility.AccessibilityEvent;
     34 import android.view.accessibility.AccessibilityNodeInfo;
     35 import android.widget.ExpandableListConnector.PositionMetadata;
     36 
     37 import java.util.ArrayList;
     38 
     39 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
     40 
     41 /**
     42  * A view that shows items in a vertically scrolling two-level list. This
     43  * differs from the {@link ListView} by allowing two levels: groups which can
     44  * individually be expanded to show its children. The items come from the
     45  * {@link ExpandableListAdapter} associated with this view.
     46  * <p>
     47  * Expandable lists are able to show an indicator beside each item to display
     48  * the item's current state (the states are usually one of expanded group,
     49  * collapsed group, child, or last child). Use
     50  * {@link #setChildIndicator(Drawable)} or {@link #setGroupIndicator(Drawable)}
     51  * (or the corresponding XML attributes) to set these indicators (see the docs
     52  * for each method to see additional state that each Drawable can have). The
     53  * default style for an {@link ExpandableListView} provides indicators which
     54  * will be shown next to Views given to the {@link ExpandableListView}. The
     55  * layouts android.R.layout.simple_expandable_list_item_1 and
     56  * android.R.layout.simple_expandable_list_item_2 (which should be used with
     57  * {@link SimpleCursorTreeAdapter}) contain the preferred position information
     58  * for indicators.
     59  * <p>
     60  * The context menu information set by an {@link ExpandableListView} will be a
     61  * {@link ExpandableListContextMenuInfo} object with
     62  * {@link ExpandableListContextMenuInfo#packedPosition} being a packed position
     63  * that can be used with {@link #getPackedPositionType(long)} and the other
     64  * similar methods.
     65  * <p>
     66  * <em><b>Note:</b></em> You cannot use the value <code>wrap_content</code>
     67  * for the <code>android:layout_height</code> attribute of a
     68  * ExpandableListView in XML if the parent's size is also not strictly specified
     69  * (for example, if the parent were ScrollView you could not specify
     70  * wrap_content since it also can be any length. However, you can use
     71  * wrap_content if the ExpandableListView parent has a specific size, such as
     72  * 100 pixels.
     73  *
     74  * @attr ref android.R.styleable#ExpandableListView_groupIndicator
     75  * @attr ref android.R.styleable#ExpandableListView_indicatorLeft
     76  * @attr ref android.R.styleable#ExpandableListView_indicatorRight
     77  * @attr ref android.R.styleable#ExpandableListView_childIndicator
     78  * @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
     79  * @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
     80  * @attr ref android.R.styleable#ExpandableListView_childDivider
     81  * @attr ref android.R.styleable#ExpandableListView_indicatorStart
     82  * @attr ref android.R.styleable#ExpandableListView_indicatorEnd
     83  * @attr ref android.R.styleable#ExpandableListView_childIndicatorStart
     84  * @attr ref android.R.styleable#ExpandableListView_childIndicatorEnd
     85  */
     86 public class ExpandableListView extends ListView {
     87 
     88     /**
     89      * The packed position represents a group.
     90      */
     91     public static final int PACKED_POSITION_TYPE_GROUP = 0;
     92 
     93     /**
     94      * The packed position represents a child.
     95      */
     96     public static final int PACKED_POSITION_TYPE_CHILD = 1;
     97 
     98     /**
     99      * The packed position represents a neither/null/no preference.
    100      */
    101     public static final int PACKED_POSITION_TYPE_NULL = 2;
    102 
    103     /**
    104      * The value for a packed position that represents neither/null/no
    105      * preference. This value is not otherwise possible since a group type
    106      * (first bit 0) should not have a child position filled.
    107      */
    108     public static final long PACKED_POSITION_VALUE_NULL = 0x00000000FFFFFFFFL;
    109 
    110     /** The mask (in packed position representation) for the child */
    111     private static final long PACKED_POSITION_MASK_CHILD = 0x00000000FFFFFFFFL;
    112 
    113     /** The mask (in packed position representation) for the group */
    114     private static final long PACKED_POSITION_MASK_GROUP = 0x7FFFFFFF00000000L;
    115 
    116     /** The mask (in packed position representation) for the type */
    117     private static final long PACKED_POSITION_MASK_TYPE  = 0x8000000000000000L;
    118 
    119     /** The shift amount (in packed position representation) for the group */
    120     private static final long PACKED_POSITION_SHIFT_GROUP = 32;
    121 
    122     /** The shift amount (in packed position representation) for the type */
    123     private static final long PACKED_POSITION_SHIFT_TYPE  = 63;
    124 
    125     /** The mask (in integer child position representation) for the child */
    126     private static final long PACKED_POSITION_INT_MASK_CHILD = 0xFFFFFFFF;
    127 
    128     /** The mask (in integer group position representation) for the group */
    129     private static final long PACKED_POSITION_INT_MASK_GROUP = 0x7FFFFFFF;
    130 
    131     /** Serves as the glue/translator between a ListView and an ExpandableListView */
    132     private ExpandableListConnector mConnector;
    133 
    134     /** Gives us Views through group+child positions */
    135     private ExpandableListAdapter mAdapter;
    136 
    137     /** Left bound for drawing the indicator. */
    138     private int mIndicatorLeft;
    139 
    140     /** Right bound for drawing the indicator. */
    141     private int mIndicatorRight;
    142 
    143     /** Start bound for drawing the indicator. */
    144     private int mIndicatorStart;
    145 
    146     /** End bound for drawing the indicator. */
    147     private int mIndicatorEnd;
    148 
    149     /**
    150      * Left bound for drawing the indicator of a child. Value of
    151      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
    152      */
    153     private int mChildIndicatorLeft;
    154 
    155     /**
    156      * Right bound for drawing the indicator of a child. Value of
    157      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorRight.
    158      */
    159     private int mChildIndicatorRight;
    160 
    161     /**
    162      * Start bound for drawing the indicator of a child. Value of
    163      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorStart.
    164      */
    165     private int mChildIndicatorStart;
    166 
    167     /**
    168      * End bound for drawing the indicator of a child. Value of
    169      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorEnd.
    170      */
    171     private int mChildIndicatorEnd;
    172 
    173     /**
    174      * Denotes when a child indicator should inherit this bound from the generic
    175      * indicator bounds
    176      */
    177     public static final int CHILD_INDICATOR_INHERIT = -1;
    178 
    179     /**
    180      * Denotes an undefined value for an indicator
    181      */
    182     private static final int INDICATOR_UNDEFINED = -2;
    183 
    184     /** The indicator drawn next to a group. */
    185     private Drawable mGroupIndicator;
    186 
    187     /** The indicator drawn next to a child. */
    188     private Drawable mChildIndicator;
    189 
    190     private static final int[] EMPTY_STATE_SET = {};
    191 
    192     /** State indicating the group is expanded. */
    193     private static final int[] GROUP_EXPANDED_STATE_SET =
    194             {R.attr.state_expanded};
    195 
    196     /** State indicating the group is empty (has no children). */
    197     private static final int[] GROUP_EMPTY_STATE_SET =
    198             {R.attr.state_empty};
    199 
    200     /** State indicating the group is expanded and empty (has no children). */
    201     private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET =
    202             {R.attr.state_expanded, R.attr.state_empty};
    203 
    204     /** States for the group where the 0th bit is expanded and 1st bit is empty. */
    205     private static final int[][] GROUP_STATE_SETS = {
    206          EMPTY_STATE_SET, // 00
    207          GROUP_EXPANDED_STATE_SET, // 01
    208          GROUP_EMPTY_STATE_SET, // 10
    209          GROUP_EXPANDED_EMPTY_STATE_SET // 11
    210     };
    211 
    212     /** State indicating the child is the last within its group. */
    213     private static final int[] CHILD_LAST_STATE_SET =
    214             {R.attr.state_last};
    215 
    216     /** Drawable to be used as a divider when it is adjacent to any children */
    217     private Drawable mChildDivider;
    218 
    219     // Bounds of the indicator to be drawn
    220     private final Rect mIndicatorRect = new Rect();
    221 
    222     public ExpandableListView(Context context) {
    223         this(context, null);
    224     }
    225 
    226     public ExpandableListView(Context context, AttributeSet attrs) {
    227         this(context, attrs, com.android.internal.R.attr.expandableListViewStyle);
    228     }
    229 
    230     public ExpandableListView(Context context, AttributeSet attrs, int defStyle) {
    231         super(context, attrs, defStyle);
    232 
    233         TypedArray a =
    234             context.obtainStyledAttributes(attrs,
    235                     com.android.internal.R.styleable.ExpandableListView, defStyle, 0);
    236 
    237         mGroupIndicator = a.getDrawable(
    238                 com.android.internal.R.styleable.ExpandableListView_groupIndicator);
    239         mChildIndicator = a.getDrawable(
    240                 com.android.internal.R.styleable.ExpandableListView_childIndicator);
    241         mIndicatorLeft = a.getDimensionPixelSize(
    242                 com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
    243         mIndicatorRight = a.getDimensionPixelSize(
    244                 com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
    245         if (mIndicatorRight == 0 && mGroupIndicator != null) {
    246             mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
    247         }
    248         mChildIndicatorLeft = a.getDimensionPixelSize(
    249                 com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft,
    250                 CHILD_INDICATOR_INHERIT);
    251         mChildIndicatorRight = a.getDimensionPixelSize(
    252                 com.android.internal.R.styleable.ExpandableListView_childIndicatorRight,
    253                 CHILD_INDICATOR_INHERIT);
    254         mChildDivider = a.getDrawable(
    255                 com.android.internal.R.styleable.ExpandableListView_childDivider);
    256 
    257         if (!isRtlCompatibilityMode()) {
    258             mIndicatorStart = a.getDimensionPixelSize(
    259                     com.android.internal.R.styleable.ExpandableListView_indicatorStart,
    260                     INDICATOR_UNDEFINED);
    261             mIndicatorEnd = a.getDimensionPixelSize(
    262                     com.android.internal.R.styleable.ExpandableListView_indicatorEnd,
    263                     INDICATOR_UNDEFINED);
    264 
    265             mChildIndicatorStart = a.getDimensionPixelSize(
    266                     com.android.internal.R.styleable.ExpandableListView_childIndicatorStart,
    267                     CHILD_INDICATOR_INHERIT);
    268             mChildIndicatorEnd = a.getDimensionPixelSize(
    269                     com.android.internal.R.styleable.ExpandableListView_childIndicatorEnd,
    270                     CHILD_INDICATOR_INHERIT);
    271         }
    272 
    273         a.recycle();
    274     }
    275 
    276     /**
    277      * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
    278      * RTL not supported)
    279      */
    280     private boolean isRtlCompatibilityMode() {
    281         final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
    282         return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport();
    283     }
    284 
    285     /**
    286      * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
    287      */
    288     private boolean hasRtlSupport() {
    289         return mContext.getApplicationInfo().hasRtlSupport();
    290     }
    291 
    292     public void onRtlPropertiesChanged(int layoutDirection) {
    293         resolveIndicator();
    294         resolveChildIndicator();
    295     }
    296 
    297     /**
    298      * Resolve start/end indicator. start/end indicator always takes precedence over left/right
    299      * indicator when defined.
    300      */
    301     private void resolveIndicator() {
    302         final boolean isLayoutRtl = isLayoutRtl();
    303         if (isLayoutRtl) {
    304             if (mIndicatorStart >= 0) {
    305                 mIndicatorRight = mIndicatorStart;
    306             }
    307             if (mIndicatorEnd >= 0) {
    308                 mIndicatorLeft = mIndicatorEnd;
    309             }
    310         } else {
    311             if (mIndicatorStart >= 0) {
    312                 mIndicatorLeft = mIndicatorStart;
    313             }
    314             if (mIndicatorEnd >= 0) {
    315                 mIndicatorRight = mIndicatorEnd;
    316             }
    317         }
    318         if (mIndicatorRight == 0 && mGroupIndicator != null) {
    319             mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
    320         }
    321     }
    322 
    323     /**
    324      * Resolve start/end child indicator. start/end child indicator always takes precedence over
    325      * left/right child indicator when defined.
    326      */
    327     private void resolveChildIndicator() {
    328         final boolean isLayoutRtl = isLayoutRtl();
    329         if (isLayoutRtl) {
    330             if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
    331                 mChildIndicatorRight = mChildIndicatorStart;
    332             }
    333             if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
    334                 mChildIndicatorLeft = mChildIndicatorEnd;
    335             }
    336         } else {
    337             if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
    338                 mChildIndicatorLeft = mChildIndicatorStart;
    339             }
    340             if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
    341                 mChildIndicatorRight = mChildIndicatorEnd;
    342             }
    343         }
    344     }
    345 
    346     @Override
    347     protected void dispatchDraw(Canvas canvas) {
    348         // Draw children, etc.
    349         super.dispatchDraw(canvas);
    350 
    351         // If we have any indicators to draw, we do it here
    352         if ((mChildIndicator == null) && (mGroupIndicator == null)) {
    353             return;
    354         }
    355 
    356         int saveCount = 0;
    357         final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    358         if (clipToPadding) {
    359             saveCount = canvas.save();
    360             final int scrollX = mScrollX;
    361             final int scrollY = mScrollY;
    362             canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
    363                     scrollX + mRight - mLeft - mPaddingRight,
    364                     scrollY + mBottom - mTop - mPaddingBottom);
    365         }
    366 
    367         final int headerViewsCount = getHeaderViewsCount();
    368 
    369         final int lastChildFlPos = mItemCount - getFooterViewsCount() - headerViewsCount - 1;
    370 
    371         final int myB = mBottom;
    372 
    373         PositionMetadata pos;
    374         View item;
    375         Drawable indicator;
    376         int t, b;
    377 
    378         // Start at a value that is neither child nor group
    379         int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
    380 
    381         final Rect indicatorRect = mIndicatorRect;
    382 
    383         // The "child" mentioned in the following two lines is this
    384         // View's child, not referring to an expandable list's
    385         // notion of a child (as opposed to a group)
    386         final int childCount = getChildCount();
    387         for (int i = 0, childFlPos = mFirstPosition - headerViewsCount; i < childCount;
    388              i++, childFlPos++) {
    389 
    390             if (childFlPos < 0) {
    391                 // This child is header
    392                 continue;
    393             } else if (childFlPos > lastChildFlPos) {
    394                 // This child is footer, so are all subsequent children
    395                 break;
    396             }
    397 
    398             item = getChildAt(i);
    399             t = item.getTop();
    400             b = item.getBottom();
    401 
    402             // This item isn't on the screen
    403             if ((b < 0) || (t > myB)) continue;
    404 
    405             // Get more expandable list-related info for this item
    406             pos = mConnector.getUnflattenedPos(childFlPos);
    407 
    408             final boolean isLayoutRtl = isLayoutRtl();
    409             final int width = getWidth();
    410 
    411             // If this item type and the previous item type are different, then we need to change
    412             // the left & right bounds
    413             if (pos.position.type != lastItemType) {
    414                 if (pos.position.type == ExpandableListPosition.CHILD) {
    415                     indicatorRect.left = (mChildIndicatorLeft == CHILD_INDICATOR_INHERIT) ?
    416                             mIndicatorLeft : mChildIndicatorLeft;
    417                     indicatorRect.right = (mChildIndicatorRight == CHILD_INDICATOR_INHERIT) ?
    418                             mIndicatorRight : mChildIndicatorRight;
    419                 } else {
    420                     indicatorRect.left = mIndicatorLeft;
    421                     indicatorRect.right = mIndicatorRight;
    422                 }
    423 
    424                 if (isLayoutRtl) {
    425                     final int temp = indicatorRect.left;
    426                     indicatorRect.left = width - indicatorRect.right;
    427                     indicatorRect.right = width - temp;
    428 
    429                     indicatorRect.left -= mPaddingRight;
    430                     indicatorRect.right -= mPaddingRight;
    431                 } else {
    432                     indicatorRect.left += mPaddingLeft;
    433                     indicatorRect.right += mPaddingLeft;
    434                 }
    435 
    436                 lastItemType = pos.position.type;
    437             }
    438 
    439             if (indicatorRect.left != indicatorRect.right) {
    440                 // Use item's full height + the divider height
    441                 if (mStackFromBottom) {
    442                     // See ListView#dispatchDraw
    443                     indicatorRect.top = t;// - mDividerHeight;
    444                     indicatorRect.bottom = b;
    445                 } else {
    446                     indicatorRect.top = t;
    447                     indicatorRect.bottom = b;// + mDividerHeight;
    448                 }
    449 
    450                 // Get the indicator (with its state set to the item's state)
    451                 indicator = getIndicator(pos);
    452                 if (indicator != null) {
    453                     // Draw the indicator
    454                     indicator.setBounds(indicatorRect);
    455                     indicator.draw(canvas);
    456                 }
    457             }
    458             pos.recycle();
    459         }
    460 
    461         if (clipToPadding) {
    462             canvas.restoreToCount(saveCount);
    463         }
    464     }
    465 
    466     /**
    467      * Gets the indicator for the item at the given position. If the indicator
    468      * is stateful, the state will be given to the indicator.
    469      *
    470      * @param pos The flat list position of the item whose indicator
    471      *            should be returned.
    472      * @return The indicator in the proper state.
    473      */
    474     private Drawable getIndicator(PositionMetadata pos) {
    475         Drawable indicator;
    476 
    477         if (pos.position.type == ExpandableListPosition.GROUP) {
    478             indicator = mGroupIndicator;
    479 
    480             if (indicator != null && indicator.isStateful()) {
    481                 // Empty check based on availability of data.  If the groupMetadata isn't null,
    482                 // we do a check on it. Otherwise, the group is collapsed so we consider it
    483                 // empty for performance reasons.
    484                 boolean isEmpty = (pos.groupMetadata == null) ||
    485                         (pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
    486 
    487                 final int stateSetIndex =
    488                     (pos.isExpanded() ? 1 : 0) | // Expanded?
    489                     (isEmpty ? 2 : 0); // Empty?
    490                 indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
    491             }
    492         } else {
    493             indicator = mChildIndicator;
    494 
    495             if (indicator != null && indicator.isStateful()) {
    496                 // No need for a state sets array for the child since it only has two states
    497                 final int stateSet[] = pos.position.flatListPos == pos.groupMetadata.lastChildFlPos
    498                         ? CHILD_LAST_STATE_SET
    499                         : EMPTY_STATE_SET;
    500                 indicator.setState(stateSet);
    501             }
    502         }
    503 
    504         return indicator;
    505     }
    506 
    507     /**
    508      * Sets the drawable that will be drawn adjacent to every child in the list. This will
    509      * be drawn using the same height as the normal divider ({@link #setDivider(Drawable)}) or
    510      * if it does not have an intrinsic height, the height set by {@link #setDividerHeight(int)}.
    511      *
    512      * @param childDivider The drawable to use.
    513      */
    514     public void setChildDivider(Drawable childDivider) {
    515         mChildDivider = childDivider;
    516     }
    517 
    518     @Override
    519     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
    520         int flatListPosition = childIndex + mFirstPosition;
    521 
    522         // Only proceed as possible child if the divider isn't above all items (if it is above
    523         // all items, then the item below it has to be a group)
    524         if (flatListPosition >= 0) {
    525             final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
    526             PositionMetadata pos = mConnector.getUnflattenedPos(adjustedPosition);
    527             // If this item is a child, or it is a non-empty group that is expanded
    528             if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
    529                     pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
    530                 // These are the cases where we draw the child divider
    531                 final Drawable divider = mChildDivider;
    532                 divider.setBounds(bounds);
    533                 divider.draw(canvas);
    534                 pos.recycle();
    535                 return;
    536             }
    537             pos.recycle();
    538         }
    539 
    540         // Otherwise draw the default divider
    541         super.drawDivider(canvas, bounds, flatListPosition);
    542     }
    543 
    544     /**
    545      * This overloaded method should not be used, instead use
    546      * {@link #setAdapter(ExpandableListAdapter)}.
    547      * <p>
    548      * {@inheritDoc}
    549      */
    550     @Override
    551     public void setAdapter(ListAdapter adapter) {
    552         throw new RuntimeException(
    553                 "For ExpandableListView, use setAdapter(ExpandableListAdapter) instead of " +
    554                 "setAdapter(ListAdapter)");
    555     }
    556 
    557     /**
    558      * This method should not be used, use {@link #getExpandableListAdapter()}.
    559      */
    560     @Override
    561     public ListAdapter getAdapter() {
    562         /*
    563          * The developer should never really call this method on an
    564          * ExpandableListView, so it would be nice to throw a RuntimeException,
    565          * but AdapterView calls this
    566          */
    567         return super.getAdapter();
    568     }
    569 
    570     /**
    571      * Register a callback to be invoked when an item has been clicked and the
    572      * caller prefers to receive a ListView-style position instead of a group
    573      * and/or child position. In most cases, the caller should use
    574      * {@link #setOnGroupClickListener} and/or {@link #setOnChildClickListener}.
    575      * <p />
    576      * {@inheritDoc}
    577      */
    578     @Override
    579     public void setOnItemClickListener(OnItemClickListener l) {
    580         super.setOnItemClickListener(l);
    581     }
    582 
    583     /**
    584      * Sets the adapter that provides data to this view.
    585      * @param adapter The adapter that provides data to this view.
    586      */
    587     public void setAdapter(ExpandableListAdapter adapter) {
    588         // Set member variable
    589         mAdapter = adapter;
    590 
    591         if (adapter != null) {
    592             // Create the connector
    593             mConnector = new ExpandableListConnector(adapter);
    594         } else {
    595             mConnector = null;
    596         }
    597 
    598         // Link the ListView (superclass) to the expandable list data through the connector
    599         super.setAdapter(mConnector);
    600     }
    601 
    602     /**
    603      * Gets the adapter that provides data to this view.
    604      * @return The adapter that provides data to this view.
    605      */
    606     public ExpandableListAdapter getExpandableListAdapter() {
    607         return mAdapter;
    608     }
    609 
    610     /**
    611      * @param position An absolute (including header and footer) flat list position.
    612      * @return true if the position corresponds to a header or a footer item.
    613      */
    614     private boolean isHeaderOrFooterPosition(int position) {
    615         final int footerViewsStart = mItemCount - getFooterViewsCount();
    616         return (position < getHeaderViewsCount() || position >= footerViewsStart);
    617     }
    618 
    619     /**
    620      * Converts an absolute item flat position into a group/child flat position, shifting according
    621      * to the number of header items.
    622      *
    623      * @param flatListPosition The absolute flat position
    624      * @return A group/child flat position as expected by the connector.
    625      */
    626     private int getFlatPositionForConnector(int flatListPosition) {
    627         return flatListPosition - getHeaderViewsCount();
    628     }
    629 
    630     /**
    631      * Converts a group/child flat position into an absolute flat position, that takes into account
    632      * the possible headers.
    633      *
    634      * @param flatListPosition The child/group flat position
    635      * @return An absolute flat position.
    636      */
    637     private int getAbsoluteFlatPosition(int flatListPosition) {
    638         return flatListPosition + getHeaderViewsCount();
    639     }
    640 
    641     @Override
    642     public boolean performItemClick(View v, int position, long id) {
    643         // Ignore clicks in header/footers
    644         if (isHeaderOrFooterPosition(position)) {
    645             // Clicked on a header/footer, so ignore pass it on to super
    646             return super.performItemClick(v, position, id);
    647         }
    648 
    649         // Internally handle the item click
    650         final int adjustedPosition = getFlatPositionForConnector(position);
    651         return handleItemClick(v, adjustedPosition, id);
    652     }
    653 
    654     /**
    655      * This will either expand/collapse groups (if a group was clicked) or pass
    656      * on the click to the proper child (if a child was clicked)
    657      *
    658      * @param position The flat list position. This has already been factored to
    659      *            remove the header/footer.
    660      * @param id The ListAdapter ID, not the group or child ID.
    661      */
    662     boolean handleItemClick(View v, int position, long id) {
    663         final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);
    664 
    665         id = getChildOrGroupId(posMetadata.position);
    666 
    667         boolean returnValue;
    668         if (posMetadata.position.type == ExpandableListPosition.GROUP) {
    669             /* It's a group, so handle collapsing/expanding */
    670 
    671             /* It's a group click, so pass on event */
    672             if (mOnGroupClickListener != null) {
    673                 if (mOnGroupClickListener.onGroupClick(this, v,
    674                         posMetadata.position.groupPos, id)) {
    675                     posMetadata.recycle();
    676                     return true;
    677                 }
    678             }
    679 
    680             if (posMetadata.isExpanded()) {
    681                 /* Collapse it */
    682                 mConnector.collapseGroup(posMetadata);
    683 
    684                 playSoundEffect(SoundEffectConstants.CLICK);
    685 
    686                 if (mOnGroupCollapseListener != null) {
    687                     mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
    688                 }
    689             } else {
    690                 /* Expand it */
    691                 mConnector.expandGroup(posMetadata);
    692 
    693                 playSoundEffect(SoundEffectConstants.CLICK);
    694 
    695                 if (mOnGroupExpandListener != null) {
    696                     mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
    697                 }
    698 
    699                 final int groupPos = posMetadata.position.groupPos;
    700                 final int groupFlatPos = posMetadata.position.flatListPos;
    701 
    702                 final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
    703                 smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
    704                         shiftedGroupPosition);
    705             }
    706 
    707             returnValue = true;
    708         } else {
    709             /* It's a child, so pass on event */
    710             if (mOnChildClickListener != null) {
    711                 playSoundEffect(SoundEffectConstants.CLICK);
    712                 return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
    713                         posMetadata.position.childPos, id);
    714             }
    715 
    716             returnValue = false;
    717         }
    718 
    719         posMetadata.recycle();
    720 
    721         return returnValue;
    722     }
    723 
    724     /**
    725      * Expand a group in the grouped list view
    726      *
    727      * @param groupPos the group to be expanded
    728      * @return True if the group was expanded, false otherwise (if the group
    729      *         was already expanded, this will return false)
    730      */
    731     public boolean expandGroup(int groupPos) {
    732        return expandGroup(groupPos, false);
    733     }
    734 
    735     /**
    736      * Expand a group in the grouped list view
    737      *
    738      * @param groupPos the group to be expanded
    739      * @param animate true if the expanding group should be animated in
    740      * @return True if the group was expanded, false otherwise (if the group
    741      *         was already expanded, this will return false)
    742      */
    743     public boolean expandGroup(int groupPos, boolean animate) {
    744         ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
    745                 ExpandableListPosition.GROUP, groupPos, -1, -1);
    746         PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
    747         elGroupPos.recycle();
    748         boolean retValue = mConnector.expandGroup(pm);
    749 
    750         if (mOnGroupExpandListener != null) {
    751             mOnGroupExpandListener.onGroupExpand(groupPos);
    752         }
    753 
    754         if (animate) {
    755             final int groupFlatPos = pm.position.flatListPos;
    756 
    757             final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
    758             smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
    759                     shiftedGroupPosition);
    760         }
    761         pm.recycle();
    762 
    763         return retValue;
    764     }
    765 
    766     /**
    767      * Collapse a group in the grouped list view
    768      *
    769      * @param groupPos position of the group to collapse
    770      * @return True if the group was collapsed, false otherwise (if the group
    771      *         was already collapsed, this will return false)
    772      */
    773     public boolean collapseGroup(int groupPos) {
    774         boolean retValue = mConnector.collapseGroup(groupPos);
    775 
    776         if (mOnGroupCollapseListener != null) {
    777             mOnGroupCollapseListener.onGroupCollapse(groupPos);
    778         }
    779 
    780         return retValue;
    781     }
    782 
    783     /** Used for being notified when a group is collapsed */
    784     public interface OnGroupCollapseListener {
    785         /**
    786          * Callback method to be invoked when a group in this expandable list has
    787          * been collapsed.
    788          *
    789          * @param groupPosition The group position that was collapsed
    790          */
    791         void onGroupCollapse(int groupPosition);
    792     }
    793 
    794     private OnGroupCollapseListener mOnGroupCollapseListener;
    795 
    796     public void setOnGroupCollapseListener(
    797             OnGroupCollapseListener onGroupCollapseListener) {
    798         mOnGroupCollapseListener = onGroupCollapseListener;
    799     }
    800 
    801     /** Used for being notified when a group is expanded */
    802     public interface OnGroupExpandListener {
    803         /**
    804          * Callback method to be invoked when a group in this expandable list has
    805          * been expanded.
    806          *
    807          * @param groupPosition The group position that was expanded
    808          */
    809         void onGroupExpand(int groupPosition);
    810     }
    811 
    812     private OnGroupExpandListener mOnGroupExpandListener;
    813 
    814     public void setOnGroupExpandListener(
    815             OnGroupExpandListener onGroupExpandListener) {
    816         mOnGroupExpandListener = onGroupExpandListener;
    817     }
    818 
    819     /**
    820      * Interface definition for a callback to be invoked when a group in this
    821      * expandable list has been clicked.
    822      */
    823     public interface OnGroupClickListener {
    824         /**
    825          * Callback method to be invoked when a group in this expandable list has
    826          * been clicked.
    827          *
    828          * @param parent The ExpandableListConnector where the click happened
    829          * @param v The view within the expandable list/ListView that was clicked
    830          * @param groupPosition The group position that was clicked
    831          * @param id The row id of the group that was clicked
    832          * @return True if the click was handled
    833          */
    834         boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
    835                 long id);
    836     }
    837 
    838     private OnGroupClickListener mOnGroupClickListener;
    839 
    840     public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
    841         mOnGroupClickListener = onGroupClickListener;
    842     }
    843 
    844     /**
    845      * Interface definition for a callback to be invoked when a child in this
    846      * expandable list has been clicked.
    847      */
    848     public interface OnChildClickListener {
    849         /**
    850          * Callback method to be invoked when a child in this expandable list has
    851          * been clicked.
    852          *
    853          * @param parent The ExpandableListView where the click happened
    854          * @param v The view within the expandable list/ListView that was clicked
    855          * @param groupPosition The group position that contains the child that
    856          *        was clicked
    857          * @param childPosition The child position within the group
    858          * @param id The row id of the child that was clicked
    859          * @return True if the click was handled
    860          */
    861         boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
    862                 int childPosition, long id);
    863     }
    864 
    865     private OnChildClickListener mOnChildClickListener;
    866 
    867     public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
    868         mOnChildClickListener = onChildClickListener;
    869     }
    870 
    871     /**
    872      * Converts a flat list position (the raw position of an item (child or group)
    873      * in the list) to a group and/or child position (represented in a
    874      * packed position). This is useful in situations where the caller needs to
    875      * use the underlying {@link ListView}'s methods. Use
    876      * {@link ExpandableListView#getPackedPositionType} ,
    877      * {@link ExpandableListView#getPackedPositionChild},
    878      * {@link ExpandableListView#getPackedPositionGroup} to unpack.
    879      *
    880      * @param flatListPosition The flat list position to be converted.
    881      * @return The group and/or child position for the given flat list position
    882      *         in packed position representation. #PACKED_POSITION_VALUE_NULL if
    883      *         the position corresponds to a header or a footer item.
    884      */
    885     public long getExpandableListPosition(int flatListPosition) {
    886         if (isHeaderOrFooterPosition(flatListPosition)) {
    887             return PACKED_POSITION_VALUE_NULL;
    888         }
    889 
    890         final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
    891         PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
    892         long packedPos = pm.position.getPackedPosition();
    893         pm.recycle();
    894         return packedPos;
    895     }
    896 
    897     /**
    898      * Converts a group and/or child position to a flat list position. This is
    899      * useful in situations where the caller needs to use the underlying
    900      * {@link ListView}'s methods.
    901      *
    902      * @param packedPosition The group and/or child positions to be converted in
    903      *            packed position representation. Use
    904      *            {@link #getPackedPositionForChild(int, int)} or
    905      *            {@link #getPackedPositionForGroup(int)}.
    906      * @return The flat list position for the given child or group.
    907      */
    908     public int getFlatListPosition(long packedPosition) {
    909         ExpandableListPosition elPackedPos = ExpandableListPosition
    910                 .obtainPosition(packedPosition);
    911         PositionMetadata pm = mConnector.getFlattenedPos(elPackedPos);
    912         elPackedPos.recycle();
    913         final int flatListPosition = pm.position.flatListPos;
    914         pm.recycle();
    915         return getAbsoluteFlatPosition(flatListPosition);
    916     }
    917 
    918     /**
    919      * Gets the position of the currently selected group or child (along with
    920      * its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
    921      *
    922      * @return A packed position containing the currently selected group or
    923      *         child's position and type. #PACKED_POSITION_VALUE_NULL if no selection
    924      *         or if selection is on a header or a footer item.
    925      */
    926     public long getSelectedPosition() {
    927         final int selectedPos = getSelectedItemPosition();
    928 
    929         // The case where there is no selection (selectedPos == -1) is also handled here.
    930         return getExpandableListPosition(selectedPos);
    931     }
    932 
    933     /**
    934      * Gets the ID of the currently selected group or child. Can return -1 if no
    935      * selection.
    936      *
    937      * @return The ID of the currently selected group or child. -1 if no
    938      *         selection.
    939      */
    940     public long getSelectedId() {
    941         long packedPos = getSelectedPosition();
    942         if (packedPos == PACKED_POSITION_VALUE_NULL) return -1;
    943 
    944         int groupPos = getPackedPositionGroup(packedPos);
    945 
    946         if (getPackedPositionType(packedPos) == PACKED_POSITION_TYPE_GROUP) {
    947             // It's a group
    948             return mAdapter.getGroupId(groupPos);
    949         } else {
    950             // It's a child
    951             return mAdapter.getChildId(groupPos, getPackedPositionChild(packedPos));
    952         }
    953     }
    954 
    955     /**
    956      * Sets the selection to the specified group.
    957      * @param groupPosition The position of the group that should be selected.
    958      */
    959     public void setSelectedGroup(int groupPosition) {
    960         ExpandableListPosition elGroupPos = ExpandableListPosition
    961                 .obtainGroupPosition(groupPosition);
    962         PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
    963         elGroupPos.recycle();
    964         final int absoluteFlatPosition = getAbsoluteFlatPosition(pm.position.flatListPos);
    965         super.setSelection(absoluteFlatPosition);
    966         pm.recycle();
    967     }
    968 
    969     /**
    970      * Sets the selection to the specified child. If the child is in a collapsed
    971      * group, the group will only be expanded and child subsequently selected if
    972      * shouldExpandGroup is set to true, otherwise the method will return false.
    973      *
    974      * @param groupPosition The position of the group that contains the child.
    975      * @param childPosition The position of the child within the group.
    976      * @param shouldExpandGroup Whether the child's group should be expanded if
    977      *            it is collapsed.
    978      * @return Whether the selection was successfully set on the child.
    979      */
    980     public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
    981         ExpandableListPosition elChildPos = ExpandableListPosition.obtainChildPosition(
    982                 groupPosition, childPosition);
    983         PositionMetadata flatChildPos = mConnector.getFlattenedPos(elChildPos);
    984 
    985         if (flatChildPos == null) {
    986             // The child's group isn't expanded
    987 
    988             // Shouldn't expand the group, so return false for we didn't set the selection
    989             if (!shouldExpandGroup) return false;
    990 
    991             expandGroup(groupPosition);
    992 
    993             flatChildPos = mConnector.getFlattenedPos(elChildPos);
    994 
    995             // Sanity check
    996             if (flatChildPos == null) {
    997                 throw new IllegalStateException("Could not find child");
    998             }
    999         }
   1000 
   1001         int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
   1002         super.setSelection(absoluteFlatPosition);
   1003 
   1004         elChildPos.recycle();
   1005         flatChildPos.recycle();
   1006 
   1007         return true;
   1008     }
   1009 
   1010     /**
   1011      * Whether the given group is currently expanded.
   1012      *
   1013      * @param groupPosition The group to check.
   1014      * @return Whether the group is currently expanded.
   1015      */
   1016     public boolean isGroupExpanded(int groupPosition) {
   1017         return mConnector.isGroupExpanded(groupPosition);
   1018     }
   1019 
   1020     /**
   1021      * Gets the type of a packed position. See
   1022      * {@link #getPackedPositionForChild(int, int)}.
   1023      *
   1024      * @param packedPosition The packed position for which to return the type.
   1025      * @return The type of the position contained within the packed position,
   1026      *         either {@link #PACKED_POSITION_TYPE_CHILD}, {@link #PACKED_POSITION_TYPE_GROUP}, or
   1027      *         {@link #PACKED_POSITION_TYPE_NULL}.
   1028      */
   1029     public static int getPackedPositionType(long packedPosition) {
   1030         if (packedPosition == PACKED_POSITION_VALUE_NULL) {
   1031             return PACKED_POSITION_TYPE_NULL;
   1032         }
   1033 
   1034         return (packedPosition & PACKED_POSITION_MASK_TYPE) == PACKED_POSITION_MASK_TYPE
   1035                 ? PACKED_POSITION_TYPE_CHILD
   1036                 : PACKED_POSITION_TYPE_GROUP;
   1037     }
   1038 
   1039     /**
   1040      * Gets the group position from a packed position. See
   1041      * {@link #getPackedPositionForChild(int, int)}.
   1042      *
   1043      * @param packedPosition The packed position from which the group position
   1044      *            will be returned.
   1045      * @return The group position portion of the packed position. If this does
   1046      *         not contain a group, returns -1.
   1047      */
   1048     public static int getPackedPositionGroup(long packedPosition) {
   1049         // Null
   1050         if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
   1051 
   1052         return (int) ((packedPosition & PACKED_POSITION_MASK_GROUP) >> PACKED_POSITION_SHIFT_GROUP);
   1053     }
   1054 
   1055     /**
   1056      * Gets the child position from a packed position that is of
   1057      * {@link #PACKED_POSITION_TYPE_CHILD} type (use {@link #getPackedPositionType(long)}).
   1058      * To get the group that this child belongs to, use
   1059      * {@link #getPackedPositionGroup(long)}. See
   1060      * {@link #getPackedPositionForChild(int, int)}.
   1061      *
   1062      * @param packedPosition The packed position from which the child position
   1063      *            will be returned.
   1064      * @return The child position portion of the packed position. If this does
   1065      *         not contain a child, returns -1.
   1066      */
   1067     public static int getPackedPositionChild(long packedPosition) {
   1068         // Null
   1069         if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
   1070 
   1071         // Group since a group type clears this bit
   1072         if ((packedPosition & PACKED_POSITION_MASK_TYPE) != PACKED_POSITION_MASK_TYPE) return -1;
   1073 
   1074         return (int) (packedPosition & PACKED_POSITION_MASK_CHILD);
   1075     }
   1076 
   1077     /**
   1078      * Returns the packed position representation of a child's position.
   1079      * <p>
   1080      * In general, a packed position should be used in
   1081      * situations where the position given to/returned from an
   1082      * {@link ExpandableListAdapter} or {@link ExpandableListView} method can
   1083      * either be a child or group. The two positions are packed into a single
   1084      * long which can be unpacked using
   1085      * {@link #getPackedPositionChild(long)},
   1086      * {@link #getPackedPositionGroup(long)}, and
   1087      * {@link #getPackedPositionType(long)}.
   1088      *
   1089      * @param groupPosition The child's parent group's position.
   1090      * @param childPosition The child position within the group.
   1091      * @return The packed position representation of the child (and parent group).
   1092      */
   1093     public static long getPackedPositionForChild(int groupPosition, int childPosition) {
   1094         return (((long)PACKED_POSITION_TYPE_CHILD) << PACKED_POSITION_SHIFT_TYPE)
   1095                 | ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
   1096                         << PACKED_POSITION_SHIFT_GROUP)
   1097                 | (childPosition & PACKED_POSITION_INT_MASK_CHILD);
   1098     }
   1099 
   1100     /**
   1101      * Returns the packed position representation of a group's position. See
   1102      * {@link #getPackedPositionForChild(int, int)}.
   1103      *
   1104      * @param groupPosition The child's parent group's position.
   1105      * @return The packed position representation of the group.
   1106      */
   1107     public static long getPackedPositionForGroup(int groupPosition) {
   1108         // No need to OR a type in because PACKED_POSITION_GROUP == 0
   1109         return ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
   1110                         << PACKED_POSITION_SHIFT_GROUP);
   1111     }
   1112 
   1113     @Override
   1114     ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
   1115         if (isHeaderOrFooterPosition(flatListPosition)) {
   1116             // Return normal info for header/footer view context menus
   1117             return new AdapterContextMenuInfo(view, flatListPosition, id);
   1118         }
   1119 
   1120         final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
   1121         PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
   1122         ExpandableListPosition pos = pm.position;
   1123 
   1124         id = getChildOrGroupId(pos);
   1125         long packedPosition = pos.getPackedPosition();
   1126 
   1127         pm.recycle();
   1128 
   1129         return new ExpandableListContextMenuInfo(view, packedPosition, id);
   1130     }
   1131 
   1132     /**
   1133      * Gets the ID of the group or child at the given <code>position</code>.
   1134      * This is useful since there is no ListAdapter ID -> ExpandableListAdapter
   1135      * ID conversion mechanism (in some cases, it isn't possible).
   1136      *
   1137      * @param position The position of the child or group whose ID should be
   1138      *            returned.
   1139      */
   1140     private long getChildOrGroupId(ExpandableListPosition position) {
   1141         if (position.type == ExpandableListPosition.CHILD) {
   1142             return mAdapter.getChildId(position.groupPos, position.childPos);
   1143         } else {
   1144             return mAdapter.getGroupId(position.groupPos);
   1145         }
   1146     }
   1147 
   1148     /**
   1149      * Sets the indicator to be drawn next to a child.
   1150      *
   1151      * @param childIndicator The drawable to be used as an indicator. If the
   1152      *            child is the last child for a group, the state
   1153      *            {@link android.R.attr#state_last} will be set.
   1154      */
   1155     public void setChildIndicator(Drawable childIndicator) {
   1156         mChildIndicator = childIndicator;
   1157     }
   1158 
   1159     /**
   1160      * Sets the drawing bounds for the child indicator. For either, you can
   1161      * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
   1162      * indicator's bounds.
   1163      *
   1164      * @see #setIndicatorBounds(int, int)
   1165      * @param left The left position (relative to the left bounds of this View)
   1166      *            to start drawing the indicator.
   1167      * @param right The right position (relative to the left bounds of this
   1168      *            View) to end the drawing of the indicator.
   1169      */
   1170     public void setChildIndicatorBounds(int left, int right) {
   1171         mChildIndicatorLeft = left;
   1172         mChildIndicatorRight = right;
   1173         resolveChildIndicator();
   1174     }
   1175 
   1176     /**
   1177      * Sets the relative drawing bounds for the child indicator. For either, you can
   1178      * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
   1179      * indicator's bounds.
   1180      *
   1181      * @see #setIndicatorBounds(int, int)
   1182      * @param start The start position (relative to the start bounds of this View)
   1183      *            to start drawing the indicator.
   1184      * @param end The end position (relative to the end bounds of this
   1185      *            View) to end the drawing of the indicator.
   1186      */
   1187     public void setChildIndicatorBoundsRelative(int start, int end) {
   1188         mChildIndicatorStart = start;
   1189         mChildIndicatorEnd = end;
   1190         resolveChildIndicator();
   1191     }
   1192 
   1193     /**
   1194      * Sets the indicator to be drawn next to a group.
   1195      *
   1196      * @param groupIndicator The drawable to be used as an indicator. If the
   1197      *            group is empty, the state {@link android.R.attr#state_empty} will be
   1198      *            set. If the group is expanded, the state
   1199      *            {@link android.R.attr#state_expanded} will be set.
   1200      */
   1201     public void setGroupIndicator(Drawable groupIndicator) {
   1202         mGroupIndicator = groupIndicator;
   1203         if (mIndicatorRight == 0 && mGroupIndicator != null) {
   1204             mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
   1205         }
   1206     }
   1207 
   1208     /**
   1209      * Sets the drawing bounds for the indicators (at minimum, the group indicator
   1210      * is affected by this; the child indicator is affected by this if the
   1211      * child indicator bounds are set to inherit).
   1212      *
   1213      * @see #setChildIndicatorBounds(int, int)
   1214      * @param left The left position (relative to the left bounds of this View)
   1215      *            to start drawing the indicator.
   1216      * @param right The right position (relative to the left bounds of this
   1217      *            View) to end the drawing of the indicator.
   1218      */
   1219     public void setIndicatorBounds(int left, int right) {
   1220         mIndicatorLeft = left;
   1221         mIndicatorRight = right;
   1222         resolveIndicator();
   1223     }
   1224 
   1225     /**
   1226      * Sets the relative drawing bounds for the indicators (at minimum, the group indicator
   1227      * is affected by this; the child indicator is affected by this if the
   1228      * child indicator bounds are set to inherit).
   1229      *
   1230      * @see #setChildIndicatorBounds(int, int)
   1231      * @param start The start position (relative to the start bounds of this View)
   1232      *            to start drawing the indicator.
   1233      * @param end The end position (relative to the end bounds of this
   1234      *            View) to end the drawing of the indicator.
   1235      */
   1236     public void setIndicatorBoundsRelative(int start, int end) {
   1237         mIndicatorStart = start;
   1238         mIndicatorEnd = end;
   1239         resolveIndicator();
   1240     }
   1241 
   1242     /**
   1243      * Extra menu information specific to an {@link ExpandableListView} provided
   1244      * to the
   1245      * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
   1246      * callback when a context menu is brought up for this AdapterView.
   1247      */
   1248     public static class ExpandableListContextMenuInfo implements ContextMenu.ContextMenuInfo {
   1249 
   1250         public ExpandableListContextMenuInfo(View targetView, long packedPosition, long id) {
   1251             this.targetView = targetView;
   1252             this.packedPosition = packedPosition;
   1253             this.id = id;
   1254         }
   1255 
   1256         /**
   1257          * The view for which the context menu is being displayed. This
   1258          * will be one of the children Views of this {@link ExpandableListView}.
   1259          */
   1260         public View targetView;
   1261 
   1262         /**
   1263          * The packed position in the list represented by the adapter for which
   1264          * the context menu is being displayed. Use the methods
   1265          * {@link ExpandableListView#getPackedPositionType},
   1266          * {@link ExpandableListView#getPackedPositionChild}, and
   1267          * {@link ExpandableListView#getPackedPositionGroup} to unpack this.
   1268          */
   1269         public long packedPosition;
   1270 
   1271         /**
   1272          * The ID of the item (group or child) for which the context menu is
   1273          * being displayed.
   1274          */
   1275         public long id;
   1276     }
   1277 
   1278     static class SavedState extends BaseSavedState {
   1279         ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList;
   1280 
   1281         /**
   1282          * Constructor called from {@link ExpandableListView#onSaveInstanceState()}
   1283          */
   1284         SavedState(
   1285                 Parcelable superState,
   1286                 ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList) {
   1287             super(superState);
   1288             this.expandedGroupMetadataList = expandedGroupMetadataList;
   1289         }
   1290 
   1291         /**
   1292          * Constructor called from {@link #CREATOR}
   1293          */
   1294         private SavedState(Parcel in) {
   1295             super(in);
   1296             expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
   1297             in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
   1298         }
   1299 
   1300         @Override
   1301         public void writeToParcel(Parcel out, int flags) {
   1302             super.writeToParcel(out, flags);
   1303             out.writeList(expandedGroupMetadataList);
   1304         }
   1305 
   1306         public static final Parcelable.Creator<SavedState> CREATOR
   1307                 = new Parcelable.Creator<SavedState>() {
   1308             public SavedState createFromParcel(Parcel in) {
   1309                 return new SavedState(in);
   1310             }
   1311 
   1312             public SavedState[] newArray(int size) {
   1313                 return new SavedState[size];
   1314             }
   1315         };
   1316     }
   1317 
   1318     @Override
   1319     public Parcelable onSaveInstanceState() {
   1320         Parcelable superState = super.onSaveInstanceState();
   1321         return new SavedState(superState,
   1322                 mConnector != null ? mConnector.getExpandedGroupMetadataList() : null);
   1323     }
   1324 
   1325     @Override
   1326     public void onRestoreInstanceState(Parcelable state) {
   1327         if (!(state instanceof SavedState)) {
   1328             super.onRestoreInstanceState(state);
   1329             return;
   1330         }
   1331 
   1332         SavedState ss = (SavedState) state;
   1333         super.onRestoreInstanceState(ss.getSuperState());
   1334 
   1335         if (mConnector != null && ss.expandedGroupMetadataList != null) {
   1336             mConnector.setExpandedGroupMetadataList(ss.expandedGroupMetadataList);
   1337         }
   1338     }
   1339 
   1340     @Override
   1341     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   1342         super.onInitializeAccessibilityEvent(event);
   1343         event.setClassName(ExpandableListView.class.getName());
   1344     }
   1345 
   1346     @Override
   1347     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   1348         super.onInitializeAccessibilityNodeInfo(info);
   1349         info.setClassName(ExpandableListView.class.getName());
   1350     }
   1351 }
   1352