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