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