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 android.os.Trace;
     20 import com.android.internal.R;
     21 import com.android.internal.util.Predicate;
     22 import com.google.android.collect.Lists;
     23 
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.res.TypedArray;
     27 import android.graphics.Canvas;
     28 import android.graphics.Paint;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.Rect;
     31 import android.graphics.drawable.Drawable;
     32 import android.util.AttributeSet;
     33 import android.util.MathUtils;
     34 import android.util.SparseBooleanArray;
     35 import android.view.FocusFinder;
     36 import android.view.KeyEvent;
     37 import android.view.SoundEffectConstants;
     38 import android.view.View;
     39 import android.view.ViewDebug;
     40 import android.view.ViewGroup;
     41 import android.view.ViewParent;
     42 import android.view.ViewRootImpl;
     43 import android.view.accessibility.AccessibilityEvent;
     44 import android.view.accessibility.AccessibilityNodeInfo;
     45 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
     46 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
     47 import android.widget.RemoteViews.RemoteView;
     48 
     49 import java.util.ArrayList;
     50 
     51 /*
     52  * Implementation Notes:
     53  *
     54  * Some terminology:
     55  *
     56  *     index    - index of the items that are currently visible
     57  *     position - index of the items in the cursor
     58  */
     59 
     60 
     61 /**
     62  * A view that shows items in a vertically scrolling list. The items
     63  * come from the {@link ListAdapter} associated with this view.
     64  *
     65  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
     66  * guide.</p>
     67  *
     68  * @attr ref android.R.styleable#ListView_entries
     69  * @attr ref android.R.styleable#ListView_divider
     70  * @attr ref android.R.styleable#ListView_dividerHeight
     71  * @attr ref android.R.styleable#ListView_headerDividersEnabled
     72  * @attr ref android.R.styleable#ListView_footerDividersEnabled
     73  */
     74 @RemoteView
     75 public class ListView extends AbsListView {
     76     /**
     77      * Used to indicate a no preference for a position type.
     78      */
     79     static final int NO_POSITION = -1;
     80 
     81     /**
     82      * When arrow scrolling, ListView will never scroll more than this factor
     83      * times the height of the list.
     84      */
     85     private static final float MAX_SCROLL_FACTOR = 0.33f;
     86 
     87     /**
     88      * When arrow scrolling, need a certain amount of pixels to preview next
     89      * items.  This is usually the fading edge, but if that is small enough,
     90      * we want to make sure we preview at least this many pixels.
     91      */
     92     private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
     93 
     94     /**
     95      * A class that represents a fixed view in a list, for example a header at the top
     96      * or a footer at the bottom.
     97      */
     98     public class FixedViewInfo {
     99         /** The view to add to the list */
    100         public View view;
    101         /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
    102         public Object data;
    103         /** <code>true</code> if the fixed view should be selectable in the list */
    104         public boolean isSelectable;
    105     }
    106 
    107     private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
    108     private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
    109 
    110     Drawable mDivider;
    111     int mDividerHeight;
    112 
    113     Drawable mOverScrollHeader;
    114     Drawable mOverScrollFooter;
    115 
    116     private boolean mIsCacheColorOpaque;
    117     private boolean mDividerIsOpaque;
    118 
    119     private boolean mHeaderDividersEnabled;
    120     private boolean mFooterDividersEnabled;
    121 
    122     private boolean mAreAllItemsSelectable = true;
    123 
    124     private boolean mItemsCanFocus = false;
    125 
    126     // used for temporary calculations.
    127     private final Rect mTempRect = new Rect();
    128     private Paint mDividerPaint;
    129 
    130     // the single allocated result per list view; kinda cheesey but avoids
    131     // allocating these thingies too often.
    132     private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
    133 
    134     // Keeps focused children visible through resizes
    135     private FocusSelector mFocusSelector;
    136 
    137     public ListView(Context context) {
    138         this(context, null);
    139     }
    140 
    141     public ListView(Context context, AttributeSet attrs) {
    142         this(context, attrs, com.android.internal.R.attr.listViewStyle);
    143     }
    144 
    145     public ListView(Context context, AttributeSet attrs, int defStyle) {
    146         super(context, attrs, defStyle);
    147 
    148         TypedArray a = context.obtainStyledAttributes(attrs,
    149                 com.android.internal.R.styleable.ListView, defStyle, 0);
    150 
    151         CharSequence[] entries = a.getTextArray(
    152                 com.android.internal.R.styleable.ListView_entries);
    153         if (entries != null) {
    154             setAdapter(new ArrayAdapter<CharSequence>(context,
    155                     com.android.internal.R.layout.simple_list_item_1, entries));
    156         }
    157 
    158         final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
    159         if (d != null) {
    160             // If a divider is specified use its intrinsic height for divider height
    161             setDivider(d);
    162         }
    163 
    164         final Drawable osHeader = a.getDrawable(
    165                 com.android.internal.R.styleable.ListView_overScrollHeader);
    166         if (osHeader != null) {
    167             setOverscrollHeader(osHeader);
    168         }
    169 
    170         final Drawable osFooter = a.getDrawable(
    171                 com.android.internal.R.styleable.ListView_overScrollFooter);
    172         if (osFooter != null) {
    173             setOverscrollFooter(osFooter);
    174         }
    175 
    176         // Use the height specified, zero being the default
    177         final int dividerHeight = a.getDimensionPixelSize(
    178                 com.android.internal.R.styleable.ListView_dividerHeight, 0);
    179         if (dividerHeight != 0) {
    180             setDividerHeight(dividerHeight);
    181         }
    182 
    183         mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
    184         mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
    185 
    186         a.recycle();
    187     }
    188 
    189     /**
    190      * @return The maximum amount a list view will scroll in response to
    191      *   an arrow event.
    192      */
    193     public int getMaxScrollAmount() {
    194         return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
    195     }
    196 
    197     /**
    198      * Make sure views are touching the top or bottom edge, as appropriate for
    199      * our gravity
    200      */
    201     private void adjustViewsUpOrDown() {
    202         final int childCount = getChildCount();
    203         int delta;
    204 
    205         if (childCount > 0) {
    206             View child;
    207 
    208             if (!mStackFromBottom) {
    209                 // Uh-oh -- we came up short. Slide all views up to make them
    210                 // align with the top
    211                 child = getChildAt(0);
    212                 delta = child.getTop() - mListPadding.top;
    213                 if (mFirstPosition != 0) {
    214                     // It's OK to have some space above the first item if it is
    215                     // part of the vertical spacing
    216                     delta -= mDividerHeight;
    217                 }
    218                 if (delta < 0) {
    219                     // We only are looking to see if we are too low, not too high
    220                     delta = 0;
    221                 }
    222             } else {
    223                 // we are too high, slide all views down to align with bottom
    224                 child = getChildAt(childCount - 1);
    225                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
    226 
    227                 if (mFirstPosition + childCount < mItemCount) {
    228                     // It's OK to have some space below the last item if it is
    229                     // part of the vertical spacing
    230                     delta += mDividerHeight;
    231                 }
    232 
    233                 if (delta > 0) {
    234                     delta = 0;
    235                 }
    236             }
    237 
    238             if (delta != 0) {
    239                 offsetChildrenTopAndBottom(-delta);
    240             }
    241         }
    242     }
    243 
    244     /**
    245      * Add a fixed view to appear at the top of the list. If this method is
    246      * called more than once, the views will appear in the order they were
    247      * added. Views added using this call can take focus if they want.
    248      * <p>
    249      * Note: When first introduced, this method could only be called before
    250      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
    251      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
    252      * called at any time. If the ListView's adapter does not extend
    253      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
    254      * instance of {@link WrapperListAdapter}.
    255      *
    256      * @param v The view to add.
    257      * @param data Data to associate with this view
    258      * @param isSelectable whether the item is selectable
    259      */
    260     public void addHeaderView(View v, Object data, boolean isSelectable) {
    261         final FixedViewInfo info = new FixedViewInfo();
    262         info.view = v;
    263         info.data = data;
    264         info.isSelectable = isSelectable;
    265         mHeaderViewInfos.add(info);
    266 
    267         // Wrap the adapter if it wasn't already wrapped.
    268         if (mAdapter != null) {
    269             if (!(mAdapter instanceof HeaderViewListAdapter)) {
    270                 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
    271             }
    272 
    273             // In the case of re-adding a header view, or adding one later on,
    274             // we need to notify the observer.
    275             if (mDataSetObserver != null) {
    276                 mDataSetObserver.onChanged();
    277             }
    278         }
    279     }
    280 
    281     /**
    282      * Add a fixed view to appear at the top of the list. If addHeaderView is
    283      * called more than once, the views will appear in the order they were
    284      * added. Views added using this call can take focus if they want.
    285      * <p>
    286      * Note: When first introduced, this method could only be called before
    287      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
    288      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
    289      * called at any time. If the ListView's adapter does not extend
    290      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
    291      * instance of {@link WrapperListAdapter}.
    292      *
    293      * @param v The view to add.
    294      */
    295     public void addHeaderView(View v) {
    296         addHeaderView(v, null, true);
    297     }
    298 
    299     @Override
    300     public int getHeaderViewsCount() {
    301         return mHeaderViewInfos.size();
    302     }
    303 
    304     /**
    305      * Removes a previously-added header view.
    306      *
    307      * @param v The view to remove
    308      * @return true if the view was removed, false if the view was not a header
    309      *         view
    310      */
    311     public boolean removeHeaderView(View v) {
    312         if (mHeaderViewInfos.size() > 0) {
    313             boolean result = false;
    314             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
    315                 if (mDataSetObserver != null) {
    316                     mDataSetObserver.onChanged();
    317                 }
    318                 result = true;
    319             }
    320             removeFixedViewInfo(v, mHeaderViewInfos);
    321             return result;
    322         }
    323         return false;
    324     }
    325 
    326     private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
    327         int len = where.size();
    328         for (int i = 0; i < len; ++i) {
    329             FixedViewInfo info = where.get(i);
    330             if (info.view == v) {
    331                 where.remove(i);
    332                 break;
    333             }
    334         }
    335     }
    336 
    337     /**
    338      * Add a fixed view to appear at the bottom of the list. If addFooterView is
    339      * called more than once, the views will appear in the order they were
    340      * added. Views added using this call can take focus if they want.
    341      * <p>
    342      * Note: When first introduced, this method could only be called before
    343      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
    344      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
    345      * called at any time. If the ListView's adapter does not extend
    346      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
    347      * instance of {@link WrapperListAdapter}.
    348      *
    349      * @param v The view to add.
    350      * @param data Data to associate with this view
    351      * @param isSelectable true if the footer view can be selected
    352      */
    353     public void addFooterView(View v, Object data, boolean isSelectable) {
    354         final FixedViewInfo info = new FixedViewInfo();
    355         info.view = v;
    356         info.data = data;
    357         info.isSelectable = isSelectable;
    358         mFooterViewInfos.add(info);
    359 
    360         // Wrap the adapter if it wasn't already wrapped.
    361         if (mAdapter != null) {
    362             if (!(mAdapter instanceof HeaderViewListAdapter)) {
    363                 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
    364             }
    365 
    366             // In the case of re-adding a footer view, or adding one later on,
    367             // we need to notify the observer.
    368             if (mDataSetObserver != null) {
    369                 mDataSetObserver.onChanged();
    370             }
    371         }
    372     }
    373 
    374     /**
    375      * Add a fixed view to appear at the bottom of the list. If addFooterView is
    376      * called more than once, the views will appear in the order they were
    377      * added. Views added using this call can take focus if they want.
    378      * <p>
    379      * Note: When first introduced, this method could only be called before
    380      * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
    381      * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
    382      * called at any time. If the ListView's adapter does not extend
    383      * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
    384      * instance of {@link WrapperListAdapter}.
    385      *
    386      * @param v The view to add.
    387      */
    388     public void addFooterView(View v) {
    389         addFooterView(v, null, true);
    390     }
    391 
    392     @Override
    393     public int getFooterViewsCount() {
    394         return mFooterViewInfos.size();
    395     }
    396 
    397     /**
    398      * Removes a previously-added footer view.
    399      *
    400      * @param v The view to remove
    401      * @return
    402      * true if the view was removed, false if the view was not a footer view
    403      */
    404     public boolean removeFooterView(View v) {
    405         if (mFooterViewInfos.size() > 0) {
    406             boolean result = false;
    407             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
    408                 if (mDataSetObserver != null) {
    409                     mDataSetObserver.onChanged();
    410                 }
    411                 result = true;
    412             }
    413             removeFixedViewInfo(v, mFooterViewInfos);
    414             return result;
    415         }
    416         return false;
    417     }
    418 
    419     /**
    420      * Returns the adapter currently in use in this ListView. The returned adapter
    421      * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
    422      * might be a {@link WrapperListAdapter}.
    423      *
    424      * @return The adapter currently used to display data in this ListView.
    425      *
    426      * @see #setAdapter(ListAdapter)
    427      */
    428     @Override
    429     public ListAdapter getAdapter() {
    430         return mAdapter;
    431     }
    432 
    433     /**
    434      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
    435      * through the specified intent.
    436      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
    437      */
    438     @android.view.RemotableViewMethod
    439     public void setRemoteViewsAdapter(Intent intent) {
    440         super.setRemoteViewsAdapter(intent);
    441     }
    442 
    443     /**
    444      * Sets the data behind this ListView.
    445      *
    446      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
    447      * depending on the ListView features currently in use. For instance, adding
    448      * headers and/or footers will cause the adapter to be wrapped.
    449      *
    450      * @param adapter The ListAdapter which is responsible for maintaining the
    451      *        data backing this list and for producing a view to represent an
    452      *        item in that data set.
    453      *
    454      * @see #getAdapter()
    455      */
    456     @Override
    457     public void setAdapter(ListAdapter adapter) {
    458         if (mAdapter != null && mDataSetObserver != null) {
    459             mAdapter.unregisterDataSetObserver(mDataSetObserver);
    460         }
    461 
    462         resetList();
    463         mRecycler.clear();
    464 
    465         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
    466             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    467         } else {
    468             mAdapter = adapter;
    469         }
    470 
    471         mOldSelectedPosition = INVALID_POSITION;
    472         mOldSelectedRowId = INVALID_ROW_ID;
    473 
    474         // AbsListView#setAdapter will update choice mode states.
    475         super.setAdapter(adapter);
    476 
    477         if (mAdapter != null) {
    478             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
    479             mOldItemCount = mItemCount;
    480             mItemCount = mAdapter.getCount();
    481             checkFocus();
    482 
    483             mDataSetObserver = new AdapterDataSetObserver();
    484             mAdapter.registerDataSetObserver(mDataSetObserver);
    485 
    486             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    487 
    488             int position;
    489             if (mStackFromBottom) {
    490                 position = lookForSelectablePosition(mItemCount - 1, false);
    491             } else {
    492                 position = lookForSelectablePosition(0, true);
    493             }
    494             setSelectedPositionInt(position);
    495             setNextSelectedPositionInt(position);
    496 
    497             if (mItemCount == 0) {
    498                 // Nothing selected
    499                 checkSelectionChanged();
    500             }
    501         } else {
    502             mAreAllItemsSelectable = true;
    503             checkFocus();
    504             // Nothing selected
    505             checkSelectionChanged();
    506         }
    507 
    508         requestLayout();
    509     }
    510 
    511     /**
    512      * The list is empty. Clear everything out.
    513      */
    514     @Override
    515     void resetList() {
    516         // The parent's resetList() will remove all views from the layout so we need to
    517         // cleanup the state of our footers and headers
    518         clearRecycledState(mHeaderViewInfos);
    519         clearRecycledState(mFooterViewInfos);
    520 
    521         super.resetList();
    522 
    523         mLayoutMode = LAYOUT_NORMAL;
    524     }
    525 
    526     private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
    527         if (infos != null) {
    528             final int count = infos.size();
    529 
    530             for (int i = 0; i < count; i++) {
    531                 final View child = infos.get(i).view;
    532                 final LayoutParams p = (LayoutParams) child.getLayoutParams();
    533                 if (p != null) {
    534                     p.recycledHeaderFooter = false;
    535                 }
    536             }
    537         }
    538     }
    539 
    540     /**
    541      * @return Whether the list needs to show the top fading edge
    542      */
    543     private boolean showingTopFadingEdge() {
    544         final int listTop = mScrollY + mListPadding.top;
    545         return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
    546     }
    547 
    548     /**
    549      * @return Whether the list needs to show the bottom fading edge
    550      */
    551     private boolean showingBottomFadingEdge() {
    552         final int childCount = getChildCount();
    553         final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    554         final int lastVisiblePosition = mFirstPosition + childCount - 1;
    555 
    556         final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
    557 
    558         return (lastVisiblePosition < mItemCount - 1)
    559                          || (bottomOfBottomChild < listBottom);
    560     }
    561 
    562 
    563     @Override
    564     public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
    565 
    566         int rectTopWithinChild = rect.top;
    567 
    568         // offset so rect is in coordinates of the this view
    569         rect.offset(child.getLeft(), child.getTop());
    570         rect.offset(-child.getScrollX(), -child.getScrollY());
    571 
    572         final int height = getHeight();
    573         int listUnfadedTop = getScrollY();
    574         int listUnfadedBottom = listUnfadedTop + height;
    575         final int fadingEdge = getVerticalFadingEdgeLength();
    576 
    577         if (showingTopFadingEdge()) {
    578             // leave room for top fading edge as long as rect isn't at very top
    579             if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
    580                 listUnfadedTop += fadingEdge;
    581             }
    582         }
    583 
    584         int childCount = getChildCount();
    585         int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    586 
    587         if (showingBottomFadingEdge()) {
    588             // leave room for bottom fading edge as long as rect isn't at very bottom
    589             if ((mSelectedPosition < mItemCount - 1)
    590                     || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
    591                 listUnfadedBottom -= fadingEdge;
    592             }
    593         }
    594 
    595         int scrollYDelta = 0;
    596 
    597         if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
    598             // need to MOVE DOWN to get it in view: move down just enough so
    599             // that the entire rectangle is in view (or at least the first
    600             // screen size chunk).
    601 
    602             if (rect.height() > height) {
    603                 // just enough to get screen size chunk on
    604                 scrollYDelta += (rect.top - listUnfadedTop);
    605             } else {
    606                 // get entire rect at bottom of screen
    607                 scrollYDelta += (rect.bottom - listUnfadedBottom);
    608             }
    609 
    610             // make sure we aren't scrolling beyond the end of our children
    611             int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
    612             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
    613         } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
    614             // need to MOVE UP to get it in view: move up just enough so that
    615             // entire rectangle is in view (or at least the first screen
    616             // size chunk of it).
    617 
    618             if (rect.height() > height) {
    619                 // screen size chunk
    620                 scrollYDelta -= (listUnfadedBottom - rect.bottom);
    621             } else {
    622                 // entire rect at top
    623                 scrollYDelta -= (listUnfadedTop - rect.top);
    624             }
    625 
    626             // make sure we aren't scrolling any further than the top our children
    627             int top = getChildAt(0).getTop();
    628             int deltaToTop = top - listUnfadedTop;
    629             scrollYDelta = Math.max(scrollYDelta, deltaToTop);
    630         }
    631 
    632         final boolean scroll = scrollYDelta != 0;
    633         if (scroll) {
    634             scrollListItemsBy(-scrollYDelta);
    635             positionSelector(INVALID_POSITION, child);
    636             mSelectedTop = child.getTop();
    637             invalidate();
    638         }
    639         return scroll;
    640     }
    641 
    642     /**
    643      * {@inheritDoc}
    644      */
    645     @Override
    646     void fillGap(boolean down) {
    647         final int count = getChildCount();
    648         if (down) {
    649             int paddingTop = 0;
    650             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    651                 paddingTop = getListPaddingTop();
    652             }
    653             final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
    654                     paddingTop;
    655             fillDown(mFirstPosition + count, startOffset);
    656             correctTooHigh(getChildCount());
    657         } else {
    658             int paddingBottom = 0;
    659             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    660                 paddingBottom = getListPaddingBottom();
    661             }
    662             final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
    663                     getHeight() - paddingBottom;
    664             fillUp(mFirstPosition - 1, startOffset);
    665             correctTooLow(getChildCount());
    666         }
    667     }
    668 
    669     /**
    670      * Fills the list from pos down to the end of the list view.
    671      *
    672      * @param pos The first position to put in the list
    673      *
    674      * @param nextTop The location where the top of the item associated with pos
    675      *        should be drawn
    676      *
    677      * @return The view that is currently selected, if it happens to be in the
    678      *         range that we draw.
    679      */
    680     private View fillDown(int pos, int nextTop) {
    681         View selectedView = null;
    682 
    683         int end = (mBottom - mTop);
    684         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    685             end -= mListPadding.bottom;
    686         }
    687 
    688         while (nextTop < end && pos < mItemCount) {
    689             // is this the selected item?
    690             boolean selected = pos == mSelectedPosition;
    691             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
    692 
    693             nextTop = child.getBottom() + mDividerHeight;
    694             if (selected) {
    695                 selectedView = child;
    696             }
    697             pos++;
    698         }
    699 
    700         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    701         return selectedView;
    702     }
    703 
    704     /**
    705      * Fills the list from pos up to the top of the list view.
    706      *
    707      * @param pos The first position to put in the list
    708      *
    709      * @param nextBottom The location where the bottom of the item associated
    710      *        with pos should be drawn
    711      *
    712      * @return The view that is currently selected
    713      */
    714     private View fillUp(int pos, int nextBottom) {
    715         View selectedView = null;
    716 
    717         int end = 0;
    718         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    719             end = mListPadding.top;
    720         }
    721 
    722         while (nextBottom > end && pos >= 0) {
    723             // is this the selected item?
    724             boolean selected = pos == mSelectedPosition;
    725             View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
    726             nextBottom = child.getTop() - mDividerHeight;
    727             if (selected) {
    728                 selectedView = child;
    729             }
    730             pos--;
    731         }
    732 
    733         mFirstPosition = pos + 1;
    734         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    735         return selectedView;
    736     }
    737 
    738     /**
    739      * Fills the list from top to bottom, starting with mFirstPosition
    740      *
    741      * @param nextTop The location where the top of the first item should be
    742      *        drawn
    743      *
    744      * @return The view that is currently selected
    745      */
    746     private View fillFromTop(int nextTop) {
    747         mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    748         mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    749         if (mFirstPosition < 0) {
    750             mFirstPosition = 0;
    751         }
    752         return fillDown(mFirstPosition, nextTop);
    753     }
    754 
    755 
    756     /**
    757      * Put mSelectedPosition in the middle of the screen and then build up and
    758      * down from there. This method forces mSelectedPosition to the center.
    759      *
    760      * @param childrenTop Top of the area in which children can be drawn, as
    761      *        measured in pixels
    762      * @param childrenBottom Bottom of the area in which children can be drawn,
    763      *        as measured in pixels
    764      * @return Currently selected view
    765      */
    766     private View fillFromMiddle(int childrenTop, int childrenBottom) {
    767         int height = childrenBottom - childrenTop;
    768 
    769         int position = reconcileSelectedPosition();
    770 
    771         View sel = makeAndAddView(position, childrenTop, true,
    772                 mListPadding.left, true);
    773         mFirstPosition = position;
    774 
    775         int selHeight = sel.getMeasuredHeight();
    776         if (selHeight <= height) {
    777             sel.offsetTopAndBottom((height - selHeight) / 2);
    778         }
    779 
    780         fillAboveAndBelow(sel, position);
    781 
    782         if (!mStackFromBottom) {
    783             correctTooHigh(getChildCount());
    784         } else {
    785             correctTooLow(getChildCount());
    786         }
    787 
    788         return sel;
    789     }
    790 
    791     /**
    792      * Once the selected view as been placed, fill up the visible area above and
    793      * below it.
    794      *
    795      * @param sel The selected view
    796      * @param position The position corresponding to sel
    797      */
    798     private void fillAboveAndBelow(View sel, int position) {
    799         final int dividerHeight = mDividerHeight;
    800         if (!mStackFromBottom) {
    801             fillUp(position - 1, sel.getTop() - dividerHeight);
    802             adjustViewsUpOrDown();
    803             fillDown(position + 1, sel.getBottom() + dividerHeight);
    804         } else {
    805             fillDown(position + 1, sel.getBottom() + dividerHeight);
    806             adjustViewsUpOrDown();
    807             fillUp(position - 1, sel.getTop() - dividerHeight);
    808         }
    809     }
    810 
    811 
    812     /**
    813      * Fills the grid based on positioning the new selection at a specific
    814      * location. The selection may be moved so that it does not intersect the
    815      * faded edges. The grid is then filled upwards and downwards from there.
    816      *
    817      * @param selectedTop Where the selected item should be
    818      * @param childrenTop Where to start drawing children
    819      * @param childrenBottom Last pixel where children can be drawn
    820      * @return The view that currently has selection
    821      */
    822     private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
    823         int fadingEdgeLength = getVerticalFadingEdgeLength();
    824         final int selectedPosition = mSelectedPosition;
    825 
    826         View sel;
    827 
    828         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    829                 selectedPosition);
    830         final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
    831                 selectedPosition);
    832 
    833         sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
    834 
    835 
    836         // Some of the newly selected item extends below the bottom of the list
    837         if (sel.getBottom() > bottomSelectionPixel) {
    838             // Find space available above the selection into which we can scroll
    839             // upwards
    840             final int spaceAbove = sel.getTop() - topSelectionPixel;
    841 
    842             // Find space required to bring the bottom of the selected item
    843             // fully into view
    844             final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    845             final int offset = Math.min(spaceAbove, spaceBelow);
    846 
    847             // Now offset the selected item to get it into view
    848             sel.offsetTopAndBottom(-offset);
    849         } else if (sel.getTop() < topSelectionPixel) {
    850             // Find space required to bring the top of the selected item fully
    851             // into view
    852             final int spaceAbove = topSelectionPixel - sel.getTop();
    853 
    854             // Find space available below the selection into which we can scroll
    855             // downwards
    856             final int spaceBelow = bottomSelectionPixel - sel.getBottom();
    857             final int offset = Math.min(spaceAbove, spaceBelow);
    858 
    859             // Offset the selected item to get it into view
    860             sel.offsetTopAndBottom(offset);
    861         }
    862 
    863         // Fill in views above and below
    864         fillAboveAndBelow(sel, selectedPosition);
    865 
    866         if (!mStackFromBottom) {
    867             correctTooHigh(getChildCount());
    868         } else {
    869             correctTooLow(getChildCount());
    870         }
    871 
    872         return sel;
    873     }
    874 
    875     /**
    876      * Calculate the bottom-most pixel we can draw the selection into
    877      *
    878      * @param childrenBottom Bottom pixel were children can be drawn
    879      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    880      * @param selectedPosition The position that will be selected
    881      * @return The bottom-most pixel we can draw the selection into
    882      */
    883     private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
    884             int selectedPosition) {
    885         int bottomSelectionPixel = childrenBottom;
    886         if (selectedPosition != mItemCount - 1) {
    887             bottomSelectionPixel -= fadingEdgeLength;
    888         }
    889         return bottomSelectionPixel;
    890     }
    891 
    892     /**
    893      * Calculate the top-most pixel we can draw the selection into
    894      *
    895      * @param childrenTop Top pixel were children can be drawn
    896      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    897      * @param selectedPosition The position that will be selected
    898      * @return The top-most pixel we can draw the selection into
    899      */
    900     private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
    901         // first pixel we can draw the selection into
    902         int topSelectionPixel = childrenTop;
    903         if (selectedPosition > 0) {
    904             topSelectionPixel += fadingEdgeLength;
    905         }
    906         return topSelectionPixel;
    907     }
    908 
    909     /**
    910      * Smoothly scroll to the specified adapter position. The view will
    911      * scroll such that the indicated position is displayed.
    912      * @param position Scroll to this adapter position.
    913      */
    914     @android.view.RemotableViewMethod
    915     public void smoothScrollToPosition(int position) {
    916         super.smoothScrollToPosition(position);
    917     }
    918 
    919     /**
    920      * Smoothly scroll to the specified adapter position offset. The view will
    921      * scroll such that the indicated position is displayed.
    922      * @param offset The amount to offset from the adapter position to scroll to.
    923      */
    924     @android.view.RemotableViewMethod
    925     public void smoothScrollByOffset(int offset) {
    926         super.smoothScrollByOffset(offset);
    927     }
    928 
    929     /**
    930      * Fills the list based on positioning the new selection relative to the old
    931      * selection. The new selection will be placed at, above, or below the
    932      * location of the new selection depending on how the selection is moving.
    933      * The selection will then be pinned to the visible part of the screen,
    934      * excluding the edges that are faded. The list is then filled upwards and
    935      * downwards from there.
    936      *
    937      * @param oldSel The old selected view. Useful for trying to put the new
    938      *        selection in the same place
    939      * @param newSel The view that is to become selected. Useful for trying to
    940      *        put the new selection in the same place
    941      * @param delta Which way we are moving
    942      * @param childrenTop Where to start drawing children
    943      * @param childrenBottom Last pixel where children can be drawn
    944      * @return The view that currently has selection
    945      */
    946     private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
    947             int childrenBottom) {
    948         int fadingEdgeLength = getVerticalFadingEdgeLength();
    949         final int selectedPosition = mSelectedPosition;
    950 
    951         View sel;
    952 
    953         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    954                 selectedPosition);
    955         final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
    956                 selectedPosition);
    957 
    958         if (delta > 0) {
    959             /*
    960              * Case 1: Scrolling down.
    961              */
    962 
    963             /*
    964              *     Before           After
    965              *    |       |        |       |
    966              *    +-------+        +-------+
    967              *    |   A   |        |   A   |
    968              *    |   1   |   =>   +-------+
    969              *    +-------+        |   B   |
    970              *    |   B   |        |   2   |
    971              *    +-------+        +-------+
    972              *    |       |        |       |
    973              *
    974              *    Try to keep the top of the previously selected item where it was.
    975              *    oldSel = A
    976              *    sel = B
    977              */
    978 
    979             // Put oldSel (A) where it belongs
    980             oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
    981                     mListPadding.left, false);
    982 
    983             final int dividerHeight = mDividerHeight;
    984 
    985             // Now put the new selection (B) below that
    986             sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
    987                     mListPadding.left, true);
    988 
    989             // Some of the newly selected item extends below the bottom of the list
    990             if (sel.getBottom() > bottomSelectionPixel) {
    991 
    992                 // Find space available above the selection into which we can scroll upwards
    993                 int spaceAbove = sel.getTop() - topSelectionPixel;
    994 
    995                 // Find space required to bring the bottom of the selected item fully into view
    996                 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    997 
    998                 // Don't scroll more than half the height of the list
    999                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
   1000                 int offset = Math.min(spaceAbove, spaceBelow);
   1001                 offset = Math.min(offset, halfVerticalSpace);
   1002 
   1003                 // We placed oldSel, so offset that item
   1004                 oldSel.offsetTopAndBottom(-offset);
   1005                 // Now offset the selected item to get it into view
   1006                 sel.offsetTopAndBottom(-offset);
   1007             }
   1008 
   1009             // Fill in views above and below
   1010             if (!mStackFromBottom) {
   1011                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
   1012                 adjustViewsUpOrDown();
   1013                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
   1014             } else {
   1015                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
   1016                 adjustViewsUpOrDown();
   1017                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
   1018             }
   1019         } else if (delta < 0) {
   1020             /*
   1021              * Case 2: Scrolling up.
   1022              */
   1023 
   1024             /*
   1025              *     Before           After
   1026              *    |       |        |       |
   1027              *    +-------+        +-------+
   1028              *    |   A   |        |   A   |
   1029              *    +-------+   =>   |   1   |
   1030              *    |   B   |        +-------+
   1031              *    |   2   |        |   B   |
   1032              *    +-------+        +-------+
   1033              *    |       |        |       |
   1034              *
   1035              *    Try to keep the top of the item about to become selected where it was.
   1036              *    newSel = A
   1037              *    olSel = B
   1038              */
   1039 
   1040             if (newSel != null) {
   1041                 // Try to position the top of newSel (A) where it was before it was selected
   1042                 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
   1043                         true);
   1044             } else {
   1045                 // If (A) was not on screen and so did not have a view, position
   1046                 // it above the oldSel (B)
   1047                 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
   1048                         true);
   1049             }
   1050 
   1051             // Some of the newly selected item extends above the top of the list
   1052             if (sel.getTop() < topSelectionPixel) {
   1053                 // Find space required to bring the top of the selected item fully into view
   1054                 int spaceAbove = topSelectionPixel - sel.getTop();
   1055 
   1056                // Find space available below the selection into which we can scroll downwards
   1057                 int spaceBelow = bottomSelectionPixel - sel.getBottom();
   1058 
   1059                 // Don't scroll more than half the height of the list
   1060                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
   1061                 int offset = Math.min(spaceAbove, spaceBelow);
   1062                 offset = Math.min(offset, halfVerticalSpace);
   1063 
   1064                 // Offset the selected item to get it into view
   1065                 sel.offsetTopAndBottom(offset);
   1066             }
   1067 
   1068             // Fill in views above and below
   1069             fillAboveAndBelow(sel, selectedPosition);
   1070         } else {
   1071 
   1072             int oldTop = oldSel.getTop();
   1073 
   1074             /*
   1075              * Case 3: Staying still
   1076              */
   1077             sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
   1078 
   1079             // We're staying still...
   1080             if (oldTop < childrenTop) {
   1081                 // ... but the top of the old selection was off screen.
   1082                 // (This can happen if the data changes size out from under us)
   1083                 int newBottom = sel.getBottom();
   1084                 if (newBottom < childrenTop + 20) {
   1085                     // Not enough visible -- bring it onscreen
   1086                     sel.offsetTopAndBottom(childrenTop - sel.getTop());
   1087                 }
   1088             }
   1089 
   1090             // Fill in views above and below
   1091             fillAboveAndBelow(sel, selectedPosition);
   1092         }
   1093 
   1094         return sel;
   1095     }
   1096 
   1097     private class FocusSelector implements Runnable {
   1098         private int mPosition;
   1099         private int mPositionTop;
   1100 
   1101         public FocusSelector setup(int position, int top) {
   1102             mPosition = position;
   1103             mPositionTop = top;
   1104             return this;
   1105         }
   1106 
   1107         public void run() {
   1108             setSelectionFromTop(mPosition, mPositionTop);
   1109         }
   1110     }
   1111 
   1112     @Override
   1113     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1114         if (getChildCount() > 0) {
   1115             View focusedChild = getFocusedChild();
   1116             if (focusedChild != null) {
   1117                 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
   1118                 final int childBottom = focusedChild.getBottom();
   1119                 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
   1120                 final int top = focusedChild.getTop() - offset;
   1121                 if (mFocusSelector == null) {
   1122                     mFocusSelector = new FocusSelector();
   1123                 }
   1124                 post(mFocusSelector.setup(childPosition, top));
   1125             }
   1126         }
   1127         super.onSizeChanged(w, h, oldw, oldh);
   1128     }
   1129 
   1130     @Override
   1131     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1132         // Sets up mListPadding
   1133         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1134 
   1135         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   1136         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   1137         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   1138         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   1139 
   1140         int childWidth = 0;
   1141         int childHeight = 0;
   1142         int childState = 0;
   1143 
   1144         mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
   1145         if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
   1146                 heightMode == MeasureSpec.UNSPECIFIED)) {
   1147             final View child = obtainView(0, mIsScrap);
   1148 
   1149             measureScrapChild(child, 0, widthMeasureSpec);
   1150 
   1151             childWidth = child.getMeasuredWidth();
   1152             childHeight = child.getMeasuredHeight();
   1153             childState = combineMeasuredStates(childState, child.getMeasuredState());
   1154 
   1155             if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
   1156                     ((LayoutParams) child.getLayoutParams()).viewType)) {
   1157                 mRecycler.addScrapView(child, -1);
   1158             }
   1159         }
   1160 
   1161         if (widthMode == MeasureSpec.UNSPECIFIED) {
   1162             widthSize = mListPadding.left + mListPadding.right + childWidth +
   1163                     getVerticalScrollbarWidth();
   1164         } else {
   1165             widthSize |= (childState&MEASURED_STATE_MASK);
   1166         }
   1167 
   1168         if (heightMode == MeasureSpec.UNSPECIFIED) {
   1169             heightSize = mListPadding.top + mListPadding.bottom + childHeight +
   1170                     getVerticalFadingEdgeLength() * 2;
   1171         }
   1172 
   1173         if (heightMode == MeasureSpec.AT_MOST) {
   1174             // TODO: after first layout we should maybe start at the first visible position, not 0
   1175             heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
   1176         }
   1177 
   1178         setMeasuredDimension(widthSize , heightSize);
   1179         mWidthMeasureSpec = widthMeasureSpec;
   1180     }
   1181 
   1182     private void measureScrapChild(View child, int position, int widthMeasureSpec) {
   1183         LayoutParams p = (LayoutParams) child.getLayoutParams();
   1184         if (p == null) {
   1185             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1186             child.setLayoutParams(p);
   1187         }
   1188         p.viewType = mAdapter.getItemViewType(position);
   1189         p.forceAdd = true;
   1190 
   1191         int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
   1192                 mListPadding.left + mListPadding.right, p.width);
   1193         int lpHeight = p.height;
   1194         int childHeightSpec;
   1195         if (lpHeight > 0) {
   1196             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   1197         } else {
   1198             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1199         }
   1200         child.measure(childWidthSpec, childHeightSpec);
   1201     }
   1202 
   1203     /**
   1204      * @return True to recycle the views used to measure this ListView in
   1205      *         UNSPECIFIED/AT_MOST modes, false otherwise.
   1206      * @hide
   1207      */
   1208     @ViewDebug.ExportedProperty(category = "list")
   1209     protected boolean recycleOnMeasure() {
   1210         return true;
   1211     }
   1212 
   1213     /**
   1214      * Measures the height of the given range of children (inclusive) and
   1215      * returns the height with this ListView's padding and divider heights
   1216      * included. If maxHeight is provided, the measuring will stop when the
   1217      * current height reaches maxHeight.
   1218      *
   1219      * @param widthMeasureSpec The width measure spec to be given to a child's
   1220      *            {@link View#measure(int, int)}.
   1221      * @param startPosition The position of the first child to be shown.
   1222      * @param endPosition The (inclusive) position of the last child to be
   1223      *            shown. Specify {@link #NO_POSITION} if the last child should be
   1224      *            the last available child from the adapter.
   1225      * @param maxHeight The maximum height that will be returned (if all the
   1226      *            children don't fit in this value, this value will be
   1227      *            returned).
   1228      * @param disallowPartialChildPosition In general, whether the returned
   1229      *            height should only contain entire children. This is more
   1230      *            powerful--it is the first inclusive position at which partial
   1231      *            children will not be allowed. Example: it looks nice to have
   1232      *            at least 3 completely visible children, and in portrait this
   1233      *            will most likely fit; but in landscape there could be times
   1234      *            when even 2 children can not be completely shown, so a value
   1235      *            of 2 (remember, inclusive) would be good (assuming
   1236      *            startPosition is 0).
   1237      * @return The height of this ListView with the given children.
   1238      */
   1239     final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
   1240             final int maxHeight, int disallowPartialChildPosition) {
   1241 
   1242         final ListAdapter adapter = mAdapter;
   1243         if (adapter == null) {
   1244             return mListPadding.top + mListPadding.bottom;
   1245         }
   1246 
   1247         // Include the padding of the list
   1248         int returnedHeight = mListPadding.top + mListPadding.bottom;
   1249         final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
   1250         // The previous height value that was less than maxHeight and contained
   1251         // no partial children
   1252         int prevHeightWithoutPartialChild = 0;
   1253         int i;
   1254         View child;
   1255 
   1256         // mItemCount - 1 since endPosition parameter is inclusive
   1257         endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
   1258         final AbsListView.RecycleBin recycleBin = mRecycler;
   1259         final boolean recyle = recycleOnMeasure();
   1260         final boolean[] isScrap = mIsScrap;
   1261 
   1262         for (i = startPosition; i <= endPosition; ++i) {
   1263             child = obtainView(i, isScrap);
   1264 
   1265             measureScrapChild(child, i, widthMeasureSpec);
   1266 
   1267             if (i > 0) {
   1268                 // Count the divider for all but one child
   1269                 returnedHeight += dividerHeight;
   1270             }
   1271 
   1272             // Recycle the view before we possibly return from the method
   1273             if (recyle && recycleBin.shouldRecycleViewType(
   1274                     ((LayoutParams) child.getLayoutParams()).viewType)) {
   1275                 recycleBin.addScrapView(child, -1);
   1276             }
   1277 
   1278             returnedHeight += child.getMeasuredHeight();
   1279 
   1280             if (returnedHeight >= maxHeight) {
   1281                 // We went over, figure out which height to return.  If returnedHeight > maxHeight,
   1282                 // then the i'th position did not fit completely.
   1283                 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
   1284                             && (i > disallowPartialChildPosition) // We've past the min pos
   1285                             && (prevHeightWithoutPartialChild > 0) // We have a prev height
   1286                             && (returnedHeight != maxHeight) // i'th child did not fit completely
   1287                         ? prevHeightWithoutPartialChild
   1288                         : maxHeight;
   1289             }
   1290 
   1291             if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
   1292                 prevHeightWithoutPartialChild = returnedHeight;
   1293             }
   1294         }
   1295 
   1296         // At this point, we went through the range of children, and they each
   1297         // completely fit, so return the returnedHeight
   1298         return returnedHeight;
   1299     }
   1300 
   1301     @Override
   1302     int findMotionRow(int y) {
   1303         int childCount = getChildCount();
   1304         if (childCount > 0) {
   1305             if (!mStackFromBottom) {
   1306                 for (int i = 0; i < childCount; i++) {
   1307                     View v = getChildAt(i);
   1308                     if (y <= v.getBottom()) {
   1309                         return mFirstPosition + i;
   1310                     }
   1311                 }
   1312             } else {
   1313                 for (int i = childCount - 1; i >= 0; i--) {
   1314                     View v = getChildAt(i);
   1315                     if (y >= v.getTop()) {
   1316                         return mFirstPosition + i;
   1317                     }
   1318                 }
   1319             }
   1320         }
   1321         return INVALID_POSITION;
   1322     }
   1323 
   1324     /**
   1325      * Put a specific item at a specific location on the screen and then build
   1326      * up and down from there.
   1327      *
   1328      * @param position The reference view to use as the starting point
   1329      * @param top Pixel offset from the top of this view to the top of the
   1330      *        reference view.
   1331      *
   1332      * @return The selected view, or null if the selected view is outside the
   1333      *         visible area.
   1334      */
   1335     private View fillSpecific(int position, int top) {
   1336         boolean tempIsSelected = position == mSelectedPosition;
   1337         View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
   1338         // Possibly changed again in fillUp if we add rows above this one.
   1339         mFirstPosition = position;
   1340 
   1341         View above;
   1342         View below;
   1343 
   1344         final int dividerHeight = mDividerHeight;
   1345         if (!mStackFromBottom) {
   1346             above = fillUp(position - 1, temp.getTop() - dividerHeight);
   1347             // This will correct for the top of the first view not touching the top of the list
   1348             adjustViewsUpOrDown();
   1349             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
   1350             int childCount = getChildCount();
   1351             if (childCount > 0) {
   1352                 correctTooHigh(childCount);
   1353             }
   1354         } else {
   1355             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
   1356             // This will correct for the bottom of the last view not touching the bottom of the list
   1357             adjustViewsUpOrDown();
   1358             above = fillUp(position - 1, temp.getTop() - dividerHeight);
   1359             int childCount = getChildCount();
   1360             if (childCount > 0) {
   1361                  correctTooLow(childCount);
   1362             }
   1363         }
   1364 
   1365         if (tempIsSelected) {
   1366             return temp;
   1367         } else if (above != null) {
   1368             return above;
   1369         } else {
   1370             return below;
   1371         }
   1372     }
   1373 
   1374     /**
   1375      * Check if we have dragged the bottom of the list too high (we have pushed the
   1376      * top element off the top of the screen when we did not need to). Correct by sliding
   1377      * everything back down.
   1378      *
   1379      * @param childCount Number of children
   1380      */
   1381     private void correctTooHigh(int childCount) {
   1382         // First see if the last item is visible. If it is not, it is OK for the
   1383         // top of the list to be pushed up.
   1384         int lastPosition = mFirstPosition + childCount - 1;
   1385         if (lastPosition == mItemCount - 1 && childCount > 0) {
   1386 
   1387             // Get the last child ...
   1388             final View lastChild = getChildAt(childCount - 1);
   1389 
   1390             // ... and its bottom edge
   1391             final int lastBottom = lastChild.getBottom();
   1392 
   1393             // This is bottom of our drawable area
   1394             final int end = (mBottom - mTop) - mListPadding.bottom;
   1395 
   1396             // This is how far the bottom edge of the last view is from the bottom of the
   1397             // drawable area
   1398             int bottomOffset = end - lastBottom;
   1399             View firstChild = getChildAt(0);
   1400             final int firstTop = firstChild.getTop();
   1401 
   1402             // Make sure we are 1) Too high, and 2) Either there are more rows above the
   1403             // first row or the first row is scrolled off the top of the drawable area
   1404             if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
   1405                 if (mFirstPosition == 0) {
   1406                     // Don't pull the top too far down
   1407                     bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
   1408                 }
   1409                 // Move everything down
   1410                 offsetChildrenTopAndBottom(bottomOffset);
   1411                 if (mFirstPosition > 0) {
   1412                     // Fill the gap that was opened above mFirstPosition with more rows, if
   1413                     // possible
   1414                     fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
   1415                     // Close up the remaining gap
   1416                     adjustViewsUpOrDown();
   1417                 }
   1418 
   1419             }
   1420         }
   1421     }
   1422 
   1423     /**
   1424      * Check if we have dragged the bottom of the list too low (we have pushed the
   1425      * bottom element off the bottom of the screen when we did not need to). Correct by sliding
   1426      * everything back up.
   1427      *
   1428      * @param childCount Number of children
   1429      */
   1430     private void correctTooLow(int childCount) {
   1431         // First see if the first item is visible. If it is not, it is OK for the
   1432         // bottom of the list to be pushed down.
   1433         if (mFirstPosition == 0 && childCount > 0) {
   1434 
   1435             // Get the first child ...
   1436             final View firstChild = getChildAt(0);
   1437 
   1438             // ... and its top edge
   1439             final int firstTop = firstChild.getTop();
   1440 
   1441             // This is top of our drawable area
   1442             final int start = mListPadding.top;
   1443 
   1444             // This is bottom of our drawable area
   1445             final int end = (mBottom - mTop) - mListPadding.bottom;
   1446 
   1447             // This is how far the top edge of the first view is from the top of the
   1448             // drawable area
   1449             int topOffset = firstTop - start;
   1450             View lastChild = getChildAt(childCount - 1);
   1451             final int lastBottom = lastChild.getBottom();
   1452             int lastPosition = mFirstPosition + childCount - 1;
   1453 
   1454             // Make sure we are 1) Too low, and 2) Either there are more rows below the
   1455             // last row or the last row is scrolled off the bottom of the drawable area
   1456             if (topOffset > 0) {
   1457                 if (lastPosition < mItemCount - 1 || lastBottom > end)  {
   1458                     if (lastPosition == mItemCount - 1) {
   1459                         // Don't pull the bottom too far up
   1460                         topOffset = Math.min(topOffset, lastBottom - end);
   1461                     }
   1462                     // Move everything up
   1463                     offsetChildrenTopAndBottom(-topOffset);
   1464                     if (lastPosition < mItemCount - 1) {
   1465                         // Fill the gap that was opened below the last position with more rows, if
   1466                         // possible
   1467                         fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
   1468                         // Close up the remaining gap
   1469                         adjustViewsUpOrDown();
   1470                     }
   1471                 } else if (lastPosition == mItemCount - 1) {
   1472                     adjustViewsUpOrDown();
   1473                 }
   1474             }
   1475         }
   1476     }
   1477 
   1478     @Override
   1479     protected void layoutChildren() {
   1480         final boolean blockLayoutRequests = mBlockLayoutRequests;
   1481         if (blockLayoutRequests) {
   1482             return;
   1483         }
   1484 
   1485         mBlockLayoutRequests = true;
   1486 
   1487         try {
   1488             super.layoutChildren();
   1489 
   1490             invalidate();
   1491 
   1492             if (mAdapter == null) {
   1493                 resetList();
   1494                 invokeOnItemScrollListener();
   1495                 return;
   1496             }
   1497 
   1498             final int childrenTop = mListPadding.top;
   1499             final int childrenBottom = mBottom - mTop - mListPadding.bottom;
   1500             final int childCount = getChildCount();
   1501 
   1502             int index = 0;
   1503             int delta = 0;
   1504 
   1505             View sel;
   1506             View oldSel = null;
   1507             View oldFirst = null;
   1508             View newSel = null;
   1509 
   1510             // Remember stuff we will need down below
   1511             switch (mLayoutMode) {
   1512             case LAYOUT_SET_SELECTION:
   1513                 index = mNextSelectedPosition - mFirstPosition;
   1514                 if (index >= 0 && index < childCount) {
   1515                     newSel = getChildAt(index);
   1516                 }
   1517                 break;
   1518             case LAYOUT_FORCE_TOP:
   1519             case LAYOUT_FORCE_BOTTOM:
   1520             case LAYOUT_SPECIFIC:
   1521             case LAYOUT_SYNC:
   1522                 break;
   1523             case LAYOUT_MOVE_SELECTION:
   1524             default:
   1525                 // Remember the previously selected view
   1526                 index = mSelectedPosition - mFirstPosition;
   1527                 if (index >= 0 && index < childCount) {
   1528                     oldSel = getChildAt(index);
   1529                 }
   1530 
   1531                 // Remember the previous first child
   1532                 oldFirst = getChildAt(0);
   1533 
   1534                 if (mNextSelectedPosition >= 0) {
   1535                     delta = mNextSelectedPosition - mSelectedPosition;
   1536                 }
   1537 
   1538                 // Caution: newSel might be null
   1539                 newSel = getChildAt(index + delta);
   1540             }
   1541 
   1542 
   1543             boolean dataChanged = mDataChanged;
   1544             if (dataChanged) {
   1545                 handleDataChanged();
   1546             }
   1547 
   1548             // Handle the empty set by removing all views that are visible
   1549             // and calling it a day
   1550             if (mItemCount == 0) {
   1551                 resetList();
   1552                 invokeOnItemScrollListener();
   1553                 return;
   1554             } else if (mItemCount != mAdapter.getCount()) {
   1555                 throw new IllegalStateException("The content of the adapter has changed but "
   1556                         + "ListView did not receive a notification. Make sure the content of "
   1557                         + "your adapter is not modified from a background thread, but only from "
   1558                         + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
   1559                         + "when its content changes. [in ListView(" + getId() + ", " + getClass()
   1560                         + ") with Adapter(" + mAdapter.getClass() + ")]");
   1561             }
   1562 
   1563             setSelectedPositionInt(mNextSelectedPosition);
   1564 
   1565             // Remember which child, if any, had accessibility focus.
   1566             final int accessibilityFocusPosition;
   1567             final View accessFocusedChild = getAccessibilityFocusedChild();
   1568             if (accessFocusedChild != null) {
   1569                 accessibilityFocusPosition = getPositionForView(accessFocusedChild);
   1570                 accessFocusedChild.setHasTransientState(true);
   1571             } else {
   1572                 accessibilityFocusPosition = INVALID_POSITION;
   1573             }
   1574 
   1575             // Ensure the child containing focus, if any, has transient state.
   1576             // If the list data hasn't changed, or if the adapter has stable
   1577             // IDs, this will maintain focus.
   1578             final View focusedChild = getFocusedChild();
   1579             if (focusedChild != null) {
   1580                 focusedChild.setHasTransientState(true);
   1581             }
   1582 
   1583             // Pull all children into the RecycleBin.
   1584             // These views will be reused if possible
   1585             final int firstPosition = mFirstPosition;
   1586             final RecycleBin recycleBin = mRecycler;
   1587             if (dataChanged) {
   1588                 for (int i = 0; i < childCount; i++) {
   1589                     recycleBin.addScrapView(getChildAt(i), firstPosition+i);
   1590                 }
   1591             } else {
   1592                 recycleBin.fillActiveViews(childCount, firstPosition);
   1593             }
   1594 
   1595             // Clear out old views
   1596             detachAllViewsFromParent();
   1597             recycleBin.removeSkippedScrap();
   1598 
   1599             switch (mLayoutMode) {
   1600             case LAYOUT_SET_SELECTION:
   1601                 if (newSel != null) {
   1602                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
   1603                 } else {
   1604                     sel = fillFromMiddle(childrenTop, childrenBottom);
   1605                 }
   1606                 break;
   1607             case LAYOUT_SYNC:
   1608                 sel = fillSpecific(mSyncPosition, mSpecificTop);
   1609                 break;
   1610             case LAYOUT_FORCE_BOTTOM:
   1611                 sel = fillUp(mItemCount - 1, childrenBottom);
   1612                 adjustViewsUpOrDown();
   1613                 break;
   1614             case LAYOUT_FORCE_TOP:
   1615                 mFirstPosition = 0;
   1616                 sel = fillFromTop(childrenTop);
   1617                 adjustViewsUpOrDown();
   1618                 break;
   1619             case LAYOUT_SPECIFIC:
   1620                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
   1621                 break;
   1622             case LAYOUT_MOVE_SELECTION:
   1623                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
   1624                 break;
   1625             default:
   1626                 if (childCount == 0) {
   1627                     if (!mStackFromBottom) {
   1628                         final int position = lookForSelectablePosition(0, true);
   1629                         setSelectedPositionInt(position);
   1630                         sel = fillFromTop(childrenTop);
   1631                     } else {
   1632                         final int position = lookForSelectablePosition(mItemCount - 1, false);
   1633                         setSelectedPositionInt(position);
   1634                         sel = fillUp(mItemCount - 1, childrenBottom);
   1635                     }
   1636                 } else {
   1637                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
   1638                         sel = fillSpecific(mSelectedPosition,
   1639                                 oldSel == null ? childrenTop : oldSel.getTop());
   1640                     } else if (mFirstPosition < mItemCount) {
   1641                         sel = fillSpecific(mFirstPosition,
   1642                                 oldFirst == null ? childrenTop : oldFirst.getTop());
   1643                     } else {
   1644                         sel = fillSpecific(0, childrenTop);
   1645                     }
   1646                 }
   1647                 break;
   1648             }
   1649 
   1650             // Flush any cached views that did not get reused above
   1651             recycleBin.scrapActiveViews();
   1652 
   1653             if (sel != null) {
   1654                 final boolean shouldPlaceFocus = mItemsCanFocus && hasFocus();
   1655                 final boolean maintainedFocus = focusedChild != null && focusedChild.hasFocus();
   1656                 if (shouldPlaceFocus && !maintainedFocus && !sel.hasFocus()) {
   1657                     if (sel.requestFocus()) {
   1658                         // Successfully placed focus, clear selection.
   1659                         sel.setSelected(false);
   1660                         mSelectorRect.setEmpty();
   1661                     } else {
   1662                         // Failed to place focus, clear current (invalid) focus.
   1663                         final View focused = getFocusedChild();
   1664                         if (focused != null) {
   1665                             focused.clearFocus();
   1666                         }
   1667                         positionSelector(INVALID_POSITION, sel);
   1668                     }
   1669                 } else {
   1670                     positionSelector(INVALID_POSITION, sel);
   1671                 }
   1672                 mSelectedTop = sel.getTop();
   1673             } else {
   1674                 // If the user's finger is down, select the motion position.
   1675                 // Otherwise, clear selection.
   1676                 if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
   1677                     final View child = getChildAt(mMotionPosition - mFirstPosition);
   1678                     if (child != null)  {
   1679                         positionSelector(mMotionPosition, child);
   1680                     }
   1681                 } else {
   1682                     mSelectedTop = 0;
   1683                     mSelectorRect.setEmpty();
   1684                 }
   1685             }
   1686 
   1687             if (accessFocusedChild != null) {
   1688                 accessFocusedChild.setHasTransientState(false);
   1689 
   1690                 // If we failed to maintain accessibility focus on the previous
   1691                 // view, attempt to restore it to the previous position.
   1692                 if (!accessFocusedChild.isAccessibilityFocused()
   1693                     && accessibilityFocusPosition != INVALID_POSITION) {
   1694                     // Bound the position within the visible children.
   1695                     final int position = MathUtils.constrain(
   1696                             accessibilityFocusPosition - mFirstPosition, 0, getChildCount() - 1);
   1697                     final View restoreView = getChildAt(position);
   1698                     if (restoreView != null) {
   1699                         restoreView.requestAccessibilityFocus();
   1700                     }
   1701                 }
   1702             }
   1703 
   1704             if (focusedChild != null) {
   1705                 focusedChild.setHasTransientState(false);
   1706             }
   1707 
   1708             mLayoutMode = LAYOUT_NORMAL;
   1709             mDataChanged = false;
   1710             if (mPositionScrollAfterLayout != null) {
   1711                 post(mPositionScrollAfterLayout);
   1712                 mPositionScrollAfterLayout = null;
   1713             }
   1714             mNeedSync = false;
   1715             setNextSelectedPositionInt(mSelectedPosition);
   1716 
   1717             updateScrollIndicators();
   1718 
   1719             if (mItemCount > 0) {
   1720                 checkSelectionChanged();
   1721             }
   1722 
   1723             invokeOnItemScrollListener();
   1724         } finally {
   1725             if (!blockLayoutRequests) {
   1726                 mBlockLayoutRequests = false;
   1727             }
   1728         }
   1729     }
   1730 
   1731     /**
   1732      * @return the direct child that contains accessibility focus, or null if no
   1733      *         child contains accessibility focus
   1734      */
   1735     private View getAccessibilityFocusedChild() {
   1736         final ViewRootImpl viewRootImpl = getViewRootImpl();
   1737         if (viewRootImpl == null) {
   1738             return null;
   1739         }
   1740 
   1741         View focusedView = viewRootImpl.getAccessibilityFocusedHost();
   1742         if (focusedView == null) {
   1743             return null;
   1744         }
   1745 
   1746         ViewParent viewParent = focusedView.getParent();
   1747         while ((viewParent instanceof View) && (viewParent != this)) {
   1748             focusedView = (View) viewParent;
   1749             viewParent = viewParent.getParent();
   1750         }
   1751 
   1752         if (!(viewParent instanceof View)) {
   1753             return null;
   1754         }
   1755 
   1756         return focusedView;
   1757     }
   1758 
   1759     /**
   1760      * Obtain the view and add it to our list of children. The view can be made
   1761      * fresh, converted from an unused view, or used as is if it was in the
   1762      * recycle bin.
   1763      *
   1764      * @param position Logical position in the list
   1765      * @param y Top or bottom edge of the view to add
   1766      * @param flow If flow is true, align top edge to y. If false, align bottom
   1767      *        edge to y.
   1768      * @param childrenLeft Left edge where children should be positioned
   1769      * @param selected Is this position selected?
   1770      * @return View that was added
   1771      */
   1772     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
   1773             boolean selected) {
   1774         View child;
   1775 
   1776 
   1777         if (!mDataChanged) {
   1778             // Try to use an existing view for this position
   1779             child = mRecycler.getActiveView(position);
   1780             if (child != null) {
   1781                 // Found it -- we're using an existing child
   1782                 // This just needs to be positioned
   1783                 setupChild(child, position, y, flow, childrenLeft, selected, true);
   1784 
   1785                 return child;
   1786             }
   1787         }
   1788 
   1789         // Make a new view for this position, or convert an unused view if possible
   1790         child = obtainView(position, mIsScrap);
   1791 
   1792         // This needs to be positioned and measured
   1793         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
   1794 
   1795         return child;
   1796     }
   1797 
   1798     /**
   1799      * Add a view as a child and make sure it is measured (if necessary) and
   1800      * positioned properly.
   1801      *
   1802      * @param child The view to add
   1803      * @param position The position of this child
   1804      * @param y The y position relative to which this view will be positioned
   1805      * @param flowDown If true, align top edge to y. If false, align bottom
   1806      *        edge to y.
   1807      * @param childrenLeft Left edge where children should be positioned
   1808      * @param selected Is this position selected?
   1809      * @param recycled Has this view been pulled from the recycle bin? If so it
   1810      *        does not need to be remeasured.
   1811      */
   1812     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
   1813             boolean selected, boolean recycled) {
   1814         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
   1815 
   1816         final boolean isSelected = selected && shouldShowSelector();
   1817         final boolean updateChildSelected = isSelected != child.isSelected();
   1818         final int mode = mTouchMode;
   1819         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
   1820                 mMotionPosition == position;
   1821         final boolean updateChildPressed = isPressed != child.isPressed();
   1822         final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
   1823 
   1824         // Respect layout params that are already in the view. Otherwise make some up...
   1825         // noinspection unchecked
   1826         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
   1827         if (p == null) {
   1828             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1829         }
   1830         p.viewType = mAdapter.getItemViewType(position);
   1831 
   1832         if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
   1833                 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
   1834             attachViewToParent(child, flowDown ? -1 : 0, p);
   1835         } else {
   1836             p.forceAdd = false;
   1837             if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   1838                 p.recycledHeaderFooter = true;
   1839             }
   1840             addViewInLayout(child, flowDown ? -1 : 0, p, true);
   1841         }
   1842 
   1843         if (updateChildSelected) {
   1844             child.setSelected(isSelected);
   1845         }
   1846 
   1847         if (updateChildPressed) {
   1848             child.setPressed(isPressed);
   1849         }
   1850 
   1851         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
   1852             if (child instanceof Checkable) {
   1853                 ((Checkable) child).setChecked(mCheckStates.get(position));
   1854             } else if (getContext().getApplicationInfo().targetSdkVersion
   1855                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
   1856                 child.setActivated(mCheckStates.get(position));
   1857             }
   1858         }
   1859 
   1860         if (needToMeasure) {
   1861             int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   1862                     mListPadding.left + mListPadding.right, p.width);
   1863             int lpHeight = p.height;
   1864             int childHeightSpec;
   1865             if (lpHeight > 0) {
   1866                 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   1867             } else {
   1868                 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1869             }
   1870             child.measure(childWidthSpec, childHeightSpec);
   1871         } else {
   1872             cleanupLayoutState(child);
   1873         }
   1874 
   1875         final int w = child.getMeasuredWidth();
   1876         final int h = child.getMeasuredHeight();
   1877         final int childTop = flowDown ? y : y - h;
   1878 
   1879         if (needToMeasure) {
   1880             final int childRight = childrenLeft + w;
   1881             final int childBottom = childTop + h;
   1882             child.layout(childrenLeft, childTop, childRight, childBottom);
   1883         } else {
   1884             child.offsetLeftAndRight(childrenLeft - child.getLeft());
   1885             child.offsetTopAndBottom(childTop - child.getTop());
   1886         }
   1887 
   1888         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
   1889             child.setDrawingCacheEnabled(true);
   1890         }
   1891 
   1892         if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
   1893                 != position) {
   1894             child.jumpDrawablesToCurrentState();
   1895         }
   1896 
   1897         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   1898     }
   1899 
   1900     @Override
   1901     protected boolean canAnimate() {
   1902         return super.canAnimate() && mItemCount > 0;
   1903     }
   1904 
   1905     /**
   1906      * Sets the currently selected item. If in touch mode, the item will not be selected
   1907      * but it will still be positioned appropriately. If the specified selection position
   1908      * is less than 0, then the item at position 0 will be selected.
   1909      *
   1910      * @param position Index (starting at 0) of the data item to be selected.
   1911      */
   1912     @Override
   1913     public void setSelection(int position) {
   1914         setSelectionFromTop(position, 0);
   1915     }
   1916 
   1917     /**
   1918      * Sets the selected item and positions the selection y pixels from the top edge
   1919      * of the ListView. (If in touch mode, the item will not be selected but it will
   1920      * still be positioned appropriately.)
   1921      *
   1922      * @param position Index (starting at 0) of the data item to be selected.
   1923      * @param y The distance from the top edge of the ListView (plus padding) that the
   1924      *        item will be positioned.
   1925      */
   1926     public void setSelectionFromTop(int position, int y) {
   1927         if (mAdapter == null) {
   1928             return;
   1929         }
   1930 
   1931         if (!isInTouchMode()) {
   1932             position = lookForSelectablePosition(position, true);
   1933             if (position >= 0) {
   1934                 setNextSelectedPositionInt(position);
   1935             }
   1936         } else {
   1937             mResurrectToPosition = position;
   1938         }
   1939 
   1940         if (position >= 0) {
   1941             mLayoutMode = LAYOUT_SPECIFIC;
   1942             mSpecificTop = mListPadding.top + y;
   1943 
   1944             if (mNeedSync) {
   1945                 mSyncPosition = position;
   1946                 mSyncRowId = mAdapter.getItemId(position);
   1947             }
   1948 
   1949             if (mPositionScroller != null) {
   1950                 mPositionScroller.stop();
   1951             }
   1952             requestLayout();
   1953         }
   1954     }
   1955 
   1956     /**
   1957      * Makes the item at the supplied position selected.
   1958      *
   1959      * @param position the position of the item to select
   1960      */
   1961     @Override
   1962     void setSelectionInt(int position) {
   1963         setNextSelectedPositionInt(position);
   1964         boolean awakeScrollbars = false;
   1965 
   1966         final int selectedPosition = mSelectedPosition;
   1967 
   1968         if (selectedPosition >= 0) {
   1969             if (position == selectedPosition - 1) {
   1970                 awakeScrollbars = true;
   1971             } else if (position == selectedPosition + 1) {
   1972                 awakeScrollbars = true;
   1973             }
   1974         }
   1975 
   1976         if (mPositionScroller != null) {
   1977             mPositionScroller.stop();
   1978         }
   1979 
   1980         layoutChildren();
   1981 
   1982         if (awakeScrollbars) {
   1983             awakenScrollBars();
   1984         }
   1985     }
   1986 
   1987     /**
   1988      * Find a position that can be selected (i.e., is not a separator).
   1989      *
   1990      * @param position The starting position to look at.
   1991      * @param lookDown Whether to look down for other positions.
   1992      * @return The next selectable position starting at position and then searching either up or
   1993      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   1994      */
   1995     @Override
   1996     int lookForSelectablePosition(int position, boolean lookDown) {
   1997         final ListAdapter adapter = mAdapter;
   1998         if (adapter == null || isInTouchMode()) {
   1999             return INVALID_POSITION;
   2000         }
   2001 
   2002         final int count = adapter.getCount();
   2003         if (!mAreAllItemsSelectable) {
   2004             if (lookDown) {
   2005                 position = Math.max(0, position);
   2006                 while (position < count && !adapter.isEnabled(position)) {
   2007                     position++;
   2008                 }
   2009             } else {
   2010                 position = Math.min(position, count - 1);
   2011                 while (position >= 0 && !adapter.isEnabled(position)) {
   2012                     position--;
   2013                 }
   2014             }
   2015         }
   2016 
   2017         if (position < 0 || position >= count) {
   2018             return INVALID_POSITION;
   2019         }
   2020 
   2021         return position;
   2022     }
   2023 
   2024     /**
   2025      * Find a position that can be selected (i.e., is not a separator). If there
   2026      * are no selectable positions in the specified direction from the starting
   2027      * position, searches in the opposite direction from the starting position
   2028      * to the current position.
   2029      *
   2030      * @param current the current position
   2031      * @param position the starting position
   2032      * @param lookDown whether to look down for other positions
   2033      * @return the next selectable position, or {@link #INVALID_POSITION} if
   2034      *         nothing can be found
   2035      */
   2036     int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
   2037         final ListAdapter adapter = mAdapter;
   2038         if (adapter == null || isInTouchMode()) {
   2039             return INVALID_POSITION;
   2040         }
   2041 
   2042         // First check after the starting position in the specified direction.
   2043         final int after = lookForSelectablePosition(position, lookDown);
   2044         if (after != INVALID_POSITION) {
   2045             return after;
   2046         }
   2047 
   2048         // Then check between the starting position and the current position.
   2049         final int count = adapter.getCount();
   2050         current = MathUtils.constrain(current, -1, count - 1);
   2051         if (lookDown) {
   2052             position = Math.min(position - 1, count - 1);
   2053             while ((position > current) && !adapter.isEnabled(position)) {
   2054                 position--;
   2055             }
   2056             if (position <= current) {
   2057                 return INVALID_POSITION;
   2058             }
   2059         } else {
   2060             position = Math.max(0, position + 1);
   2061             while ((position < current) && !adapter.isEnabled(position)) {
   2062                 position++;
   2063             }
   2064             if (position >= current) {
   2065                 return INVALID_POSITION;
   2066             }
   2067         }
   2068 
   2069         return position;
   2070     }
   2071 
   2072     /**
   2073      * setSelectionAfterHeaderView set the selection to be the first list item
   2074      * after the header views.
   2075      */
   2076     public void setSelectionAfterHeaderView() {
   2077         final int count = mHeaderViewInfos.size();
   2078         if (count > 0) {
   2079             mNextSelectedPosition = 0;
   2080             return;
   2081         }
   2082 
   2083         if (mAdapter != null) {
   2084             setSelection(count);
   2085         } else {
   2086             mNextSelectedPosition = count;
   2087             mLayoutMode = LAYOUT_SET_SELECTION;
   2088         }
   2089 
   2090     }
   2091 
   2092     @Override
   2093     public boolean dispatchKeyEvent(KeyEvent event) {
   2094         // Dispatch in the normal way
   2095         boolean handled = super.dispatchKeyEvent(event);
   2096         if (!handled) {
   2097             // If we didn't handle it...
   2098             View focused = getFocusedChild();
   2099             if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
   2100                 // ... and our focused child didn't handle it
   2101                 // ... give it to ourselves so we can scroll if necessary
   2102                 handled = onKeyDown(event.getKeyCode(), event);
   2103             }
   2104         }
   2105         return handled;
   2106     }
   2107 
   2108     @Override
   2109     public boolean onKeyDown(int keyCode, KeyEvent event) {
   2110         return commonKey(keyCode, 1, event);
   2111     }
   2112 
   2113     @Override
   2114     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   2115         return commonKey(keyCode, repeatCount, event);
   2116     }
   2117 
   2118     @Override
   2119     public boolean onKeyUp(int keyCode, KeyEvent event) {
   2120         return commonKey(keyCode, 1, event);
   2121     }
   2122 
   2123     private boolean commonKey(int keyCode, int count, KeyEvent event) {
   2124         if (mAdapter == null || !isAttachedToWindow()) {
   2125             return false;
   2126         }
   2127 
   2128         if (mDataChanged) {
   2129             layoutChildren();
   2130         }
   2131 
   2132         boolean handled = false;
   2133         int action = event.getAction();
   2134 
   2135         if (action != KeyEvent.ACTION_UP) {
   2136             switch (keyCode) {
   2137             case KeyEvent.KEYCODE_DPAD_UP:
   2138                 if (event.hasNoModifiers()) {
   2139                     handled = resurrectSelectionIfNeeded();
   2140                     if (!handled) {
   2141                         while (count-- > 0) {
   2142                             if (arrowScroll(FOCUS_UP)) {
   2143                                 handled = true;
   2144                             } else {
   2145                                 break;
   2146                             }
   2147                         }
   2148                     }
   2149                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2150                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2151                 }
   2152                 break;
   2153 
   2154             case KeyEvent.KEYCODE_DPAD_DOWN:
   2155                 if (event.hasNoModifiers()) {
   2156                     handled = resurrectSelectionIfNeeded();
   2157                     if (!handled) {
   2158                         while (count-- > 0) {
   2159                             if (arrowScroll(FOCUS_DOWN)) {
   2160                                 handled = true;
   2161                             } else {
   2162                                 break;
   2163                             }
   2164                         }
   2165                     }
   2166                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2167                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2168                 }
   2169                 break;
   2170 
   2171             case KeyEvent.KEYCODE_DPAD_LEFT:
   2172                 if (event.hasNoModifiers()) {
   2173                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
   2174                 }
   2175                 break;
   2176 
   2177             case KeyEvent.KEYCODE_DPAD_RIGHT:
   2178                 if (event.hasNoModifiers()) {
   2179                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
   2180                 }
   2181                 break;
   2182 
   2183             case KeyEvent.KEYCODE_DPAD_CENTER:
   2184             case KeyEvent.KEYCODE_ENTER:
   2185                 if (event.hasNoModifiers()) {
   2186                     handled = resurrectSelectionIfNeeded();
   2187                     if (!handled
   2188                             && event.getRepeatCount() == 0 && getChildCount() > 0) {
   2189                         keyPressed();
   2190                         handled = true;
   2191                     }
   2192                 }
   2193                 break;
   2194 
   2195             case KeyEvent.KEYCODE_SPACE:
   2196                 if (mPopup == null || !mPopup.isShowing()) {
   2197                     if (event.hasNoModifiers()) {
   2198                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2199                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2200                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2201                     }
   2202                     handled = true;
   2203                 }
   2204                 break;
   2205 
   2206             case KeyEvent.KEYCODE_PAGE_UP:
   2207                 if (event.hasNoModifiers()) {
   2208                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2209                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2210                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2211                 }
   2212                 break;
   2213 
   2214             case KeyEvent.KEYCODE_PAGE_DOWN:
   2215                 if (event.hasNoModifiers()) {
   2216                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2217                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2218                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2219                 }
   2220                 break;
   2221 
   2222             case KeyEvent.KEYCODE_MOVE_HOME:
   2223                 if (event.hasNoModifiers()) {
   2224                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2225                 }
   2226                 break;
   2227 
   2228             case KeyEvent.KEYCODE_MOVE_END:
   2229                 if (event.hasNoModifiers()) {
   2230                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2231                 }
   2232                 break;
   2233 
   2234             case KeyEvent.KEYCODE_TAB:
   2235                 // XXX Sometimes it is useful to be able to TAB through the items in
   2236                 //     a ListView sequentially.  Unfortunately this can create an
   2237                 //     asymmetry in TAB navigation order unless the list selection
   2238                 //     always reverts to the top or bottom when receiving TAB focus from
   2239                 //     another widget.  Leaving this behavior disabled for now but
   2240                 //     perhaps it should be configurable (and more comprehensive).
   2241                 if (false) {
   2242                     if (event.hasNoModifiers()) {
   2243                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
   2244                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2245                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
   2246                     }
   2247                 }
   2248                 break;
   2249             }
   2250         }
   2251 
   2252         if (handled) {
   2253             return true;
   2254         }
   2255 
   2256         if (sendToTextFilter(keyCode, count, event)) {
   2257             return true;
   2258         }
   2259 
   2260         switch (action) {
   2261             case KeyEvent.ACTION_DOWN:
   2262                 return super.onKeyDown(keyCode, event);
   2263 
   2264             case KeyEvent.ACTION_UP:
   2265                 return super.onKeyUp(keyCode, event);
   2266 
   2267             case KeyEvent.ACTION_MULTIPLE:
   2268                 return super.onKeyMultiple(keyCode, count, event);
   2269 
   2270             default: // shouldn't happen
   2271                 return false;
   2272         }
   2273     }
   2274 
   2275     /**
   2276      * Scrolls up or down by the number of items currently present on screen.
   2277      *
   2278      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2279      * @return whether selection was moved
   2280      */
   2281     boolean pageScroll(int direction) {
   2282         final int nextPage;
   2283         final boolean down;
   2284 
   2285         if (direction == FOCUS_UP) {
   2286             nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
   2287             down = false;
   2288         } else if (direction == FOCUS_DOWN) {
   2289             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
   2290             down = true;
   2291         } else {
   2292             return false;
   2293         }
   2294 
   2295         if (nextPage >= 0) {
   2296             final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
   2297             if (position >= 0) {
   2298                 mLayoutMode = LAYOUT_SPECIFIC;
   2299                 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
   2300 
   2301                 if (down && (position > (mItemCount - getChildCount()))) {
   2302                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2303                 }
   2304 
   2305                 if (!down && (position < getChildCount())) {
   2306                     mLayoutMode = LAYOUT_FORCE_TOP;
   2307                 }
   2308 
   2309                 setSelectionInt(position);
   2310                 invokeOnItemScrollListener();
   2311                 if (!awakenScrollBars()) {
   2312                     invalidate();
   2313                 }
   2314 
   2315                 return true;
   2316             }
   2317         }
   2318 
   2319         return false;
   2320     }
   2321 
   2322     /**
   2323      * Go to the last or first item if possible (not worrying about panning
   2324      * across or navigating within the internal focus of the currently selected
   2325      * item.)
   2326      *
   2327      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2328      * @return whether selection was moved
   2329      */
   2330     boolean fullScroll(int direction) {
   2331         boolean moved = false;
   2332         if (direction == FOCUS_UP) {
   2333             if (mSelectedPosition != 0) {
   2334                 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
   2335                 if (position >= 0) {
   2336                     mLayoutMode = LAYOUT_FORCE_TOP;
   2337                     setSelectionInt(position);
   2338                     invokeOnItemScrollListener();
   2339                 }
   2340                 moved = true;
   2341             }
   2342         } else if (direction == FOCUS_DOWN) {
   2343             final int lastItem = (mItemCount - 1);
   2344             if (mSelectedPosition < lastItem) {
   2345                 final int position = lookForSelectablePositionAfter(
   2346                         mSelectedPosition, lastItem, false);
   2347                 if (position >= 0) {
   2348                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2349                     setSelectionInt(position);
   2350                     invokeOnItemScrollListener();
   2351                 }
   2352                 moved = true;
   2353             }
   2354         }
   2355 
   2356         if (moved && !awakenScrollBars()) {
   2357             awakenScrollBars();
   2358             invalidate();
   2359         }
   2360 
   2361         return moved;
   2362     }
   2363 
   2364     /**
   2365      * To avoid horizontal focus searches changing the selected item, we
   2366      * manually focus search within the selected item (as applicable), and
   2367      * prevent focus from jumping to something within another item.
   2368      * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
   2369      * @return Whether this consumes the key event.
   2370      */
   2371     private boolean handleHorizontalFocusWithinListItem(int direction) {
   2372         if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
   2373             throw new IllegalArgumentException("direction must be one of"
   2374                     + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
   2375         }
   2376 
   2377         final int numChildren = getChildCount();
   2378         if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
   2379             final View selectedView = getSelectedView();
   2380             if (selectedView != null && selectedView.hasFocus() &&
   2381                     selectedView instanceof ViewGroup) {
   2382 
   2383                 final View currentFocus = selectedView.findFocus();
   2384                 final View nextFocus = FocusFinder.getInstance().findNextFocus(
   2385                         (ViewGroup) selectedView, currentFocus, direction);
   2386                 if (nextFocus != null) {
   2387                     // do the math to get interesting rect in next focus' coordinates
   2388                     currentFocus.getFocusedRect(mTempRect);
   2389                     offsetDescendantRectToMyCoords(currentFocus, mTempRect);
   2390                     offsetRectIntoDescendantCoords(nextFocus, mTempRect);
   2391                     if (nextFocus.requestFocus(direction, mTempRect)) {
   2392                         return true;
   2393                     }
   2394                 }
   2395                 // we are blocking the key from being handled (by returning true)
   2396                 // if the global result is going to be some other view within this
   2397                 // list.  this is to acheive the overall goal of having
   2398                 // horizontal d-pad navigation remain in the current item.
   2399                 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
   2400                         (ViewGroup) getRootView(), currentFocus, direction);
   2401                 if (globalNextFocus != null) {
   2402                     return isViewAncestorOf(globalNextFocus, this);
   2403                 }
   2404             }
   2405         }
   2406         return false;
   2407     }
   2408 
   2409     /**
   2410      * Scrolls to the next or previous item if possible.
   2411      *
   2412      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2413      *
   2414      * @return whether selection was moved
   2415      */
   2416     boolean arrowScroll(int direction) {
   2417         try {
   2418             mInLayout = true;
   2419             final boolean handled = arrowScrollImpl(direction);
   2420             if (handled) {
   2421                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
   2422             }
   2423             return handled;
   2424         } finally {
   2425             mInLayout = false;
   2426         }
   2427     }
   2428 
   2429     /**
   2430      * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
   2431      * to move to. This return a position in the direction given if the selected item
   2432      * is fully visible.
   2433      *
   2434      * @param selectedView Current selected view to move from
   2435      * @param selectedPos Current selected position to move from
   2436      * @param direction Direction to move in
   2437      * @return Desired selected position after moving in the given direction
   2438      */
   2439     private final int nextSelectedPositionForDirection(
   2440             View selectedView, int selectedPos, int direction) {
   2441         int nextSelected;
   2442 
   2443         if (direction == View.FOCUS_DOWN) {
   2444             final int listBottom = getHeight() - mListPadding.bottom;
   2445             if (selectedView != null && selectedView.getBottom() <= listBottom) {
   2446                 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
   2447                         selectedPos + 1 :
   2448                         mFirstPosition;
   2449             } else {
   2450                 return INVALID_POSITION;
   2451             }
   2452         } else {
   2453             final int listTop = mListPadding.top;
   2454             if (selectedView != null && selectedView.getTop() >= listTop) {
   2455                 final int lastPos = mFirstPosition + getChildCount() - 1;
   2456                 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
   2457                         selectedPos - 1 :
   2458                         lastPos;
   2459             } else {
   2460                 return INVALID_POSITION;
   2461             }
   2462         }
   2463 
   2464         if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
   2465             return INVALID_POSITION;
   2466         }
   2467         return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
   2468     }
   2469 
   2470     /**
   2471      * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
   2472      * whether there are focusable items etc.
   2473      *
   2474      * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
   2475      * @return Whether any scrolling, selection or focus change occured.
   2476      */
   2477     private boolean arrowScrollImpl(int direction) {
   2478         if (getChildCount() <= 0) {
   2479             return false;
   2480         }
   2481 
   2482         View selectedView = getSelectedView();
   2483         int selectedPos = mSelectedPosition;
   2484 
   2485         int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
   2486         int amountToScroll = amountToScroll(direction, nextSelectedPosition);
   2487 
   2488         // if we are moving focus, we may OVERRIDE the default behavior
   2489         final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
   2490         if (focusResult != null) {
   2491             nextSelectedPosition = focusResult.getSelectedPosition();
   2492             amountToScroll = focusResult.getAmountToScroll();
   2493         }
   2494 
   2495         boolean needToRedraw = focusResult != null;
   2496         if (nextSelectedPosition != INVALID_POSITION) {
   2497             handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
   2498             setSelectedPositionInt(nextSelectedPosition);
   2499             setNextSelectedPositionInt(nextSelectedPosition);
   2500             selectedView = getSelectedView();
   2501             selectedPos = nextSelectedPosition;
   2502             if (mItemsCanFocus && focusResult == null) {
   2503                 // there was no new view found to take focus, make sure we
   2504                 // don't leave focus with the old selection
   2505                 final View focused = getFocusedChild();
   2506                 if (focused != null) {
   2507                     focused.clearFocus();
   2508                 }
   2509             }
   2510             needToRedraw = true;
   2511             checkSelectionChanged();
   2512         }
   2513 
   2514         if (amountToScroll > 0) {
   2515             scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
   2516             needToRedraw = true;
   2517         }
   2518 
   2519         // if we didn't find a new focusable, make sure any existing focused
   2520         // item that was panned off screen gives up focus.
   2521         if (mItemsCanFocus && (focusResult == null)
   2522                 && selectedView != null && selectedView.hasFocus()) {
   2523             final View focused = selectedView.findFocus();
   2524             if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
   2525                 focused.clearFocus();
   2526             }
   2527         }
   2528 
   2529         // if  the current selection is panned off, we need to remove the selection
   2530         if (nextSelectedPosition == INVALID_POSITION && selectedView != null
   2531                 && !isViewAncestorOf(selectedView, this)) {
   2532             selectedView = null;
   2533             hideSelector();
   2534 
   2535             // but we don't want to set the ressurect position (that would make subsequent
   2536             // unhandled key events bring back the item we just scrolled off!)
   2537             mResurrectToPosition = INVALID_POSITION;
   2538         }
   2539 
   2540         if (needToRedraw) {
   2541             if (selectedView != null) {
   2542                 positionSelector(selectedPos, selectedView);
   2543                 mSelectedTop = selectedView.getTop();
   2544             }
   2545             if (!awakenScrollBars()) {
   2546                 invalidate();
   2547             }
   2548             invokeOnItemScrollListener();
   2549             return true;
   2550         }
   2551 
   2552         return false;
   2553     }
   2554 
   2555     /**
   2556      * When selection changes, it is possible that the previously selected or the
   2557      * next selected item will change its size.  If so, we need to offset some folks,
   2558      * and re-layout the items as appropriate.
   2559      *
   2560      * @param selectedView The currently selected view (before changing selection).
   2561      *   should be <code>null</code> if there was no previous selection.
   2562      * @param direction Either {@link android.view.View#FOCUS_UP} or
   2563      *        {@link android.view.View#FOCUS_DOWN}.
   2564      * @param newSelectedPosition The position of the next selection.
   2565      * @param newFocusAssigned whether new focus was assigned.  This matters because
   2566      *        when something has focus, we don't want to show selection (ugh).
   2567      */
   2568     private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
   2569             boolean newFocusAssigned) {
   2570         if (newSelectedPosition == INVALID_POSITION) {
   2571             throw new IllegalArgumentException("newSelectedPosition needs to be valid");
   2572         }
   2573 
   2574         // whether or not we are moving down or up, we want to preserve the
   2575         // top of whatever view is on top:
   2576         // - moving down: the view that had selection
   2577         // - moving up: the view that is getting selection
   2578         View topView;
   2579         View bottomView;
   2580         int topViewIndex, bottomViewIndex;
   2581         boolean topSelected = false;
   2582         final int selectedIndex = mSelectedPosition - mFirstPosition;
   2583         final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
   2584         if (direction == View.FOCUS_UP) {
   2585             topViewIndex = nextSelectedIndex;
   2586             bottomViewIndex = selectedIndex;
   2587             topView = getChildAt(topViewIndex);
   2588             bottomView = selectedView;
   2589             topSelected = true;
   2590         } else {
   2591             topViewIndex = selectedIndex;
   2592             bottomViewIndex = nextSelectedIndex;
   2593             topView = selectedView;
   2594             bottomView = getChildAt(bottomViewIndex);
   2595         }
   2596 
   2597         final int numChildren = getChildCount();
   2598 
   2599         // start with top view: is it changing size?
   2600         if (topView != null) {
   2601             topView.setSelected(!newFocusAssigned && topSelected);
   2602             measureAndAdjustDown(topView, topViewIndex, numChildren);
   2603         }
   2604 
   2605         // is the bottom view changing size?
   2606         if (bottomView != null) {
   2607             bottomView.setSelected(!newFocusAssigned && !topSelected);
   2608             measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
   2609         }
   2610     }
   2611 
   2612     /**
   2613      * Re-measure a child, and if its height changes, lay it out preserving its
   2614      * top, and adjust the children below it appropriately.
   2615      * @param child The child
   2616      * @param childIndex The view group index of the child.
   2617      * @param numChildren The number of children in the view group.
   2618      */
   2619     private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
   2620         int oldHeight = child.getHeight();
   2621         measureItem(child);
   2622         if (child.getMeasuredHeight() != oldHeight) {
   2623             // lay out the view, preserving its top
   2624             relayoutMeasuredItem(child);
   2625 
   2626             // adjust views below appropriately
   2627             final int heightDelta = child.getMeasuredHeight() - oldHeight;
   2628             for (int i = childIndex + 1; i < numChildren; i++) {
   2629                 getChildAt(i).offsetTopAndBottom(heightDelta);
   2630             }
   2631         }
   2632     }
   2633 
   2634     /**
   2635      * Measure a particular list child.
   2636      * TODO: unify with setUpChild.
   2637      * @param child The child.
   2638      */
   2639     private void measureItem(View child) {
   2640         ViewGroup.LayoutParams p = child.getLayoutParams();
   2641         if (p == null) {
   2642             p = new ViewGroup.LayoutParams(
   2643                     ViewGroup.LayoutParams.MATCH_PARENT,
   2644                     ViewGroup.LayoutParams.WRAP_CONTENT);
   2645         }
   2646 
   2647         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   2648                 mListPadding.left + mListPadding.right, p.width);
   2649         int lpHeight = p.height;
   2650         int childHeightSpec;
   2651         if (lpHeight > 0) {
   2652             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   2653         } else {
   2654             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   2655         }
   2656         child.measure(childWidthSpec, childHeightSpec);
   2657     }
   2658 
   2659     /**
   2660      * Layout a child that has been measured, preserving its top position.
   2661      * TODO: unify with setUpChild.
   2662      * @param child The child.
   2663      */
   2664     private void relayoutMeasuredItem(View child) {
   2665         final int w = child.getMeasuredWidth();
   2666         final int h = child.getMeasuredHeight();
   2667         final int childLeft = mListPadding.left;
   2668         final int childRight = childLeft + w;
   2669         final int childTop = child.getTop();
   2670         final int childBottom = childTop + h;
   2671         child.layout(childLeft, childTop, childRight, childBottom);
   2672     }
   2673 
   2674     /**
   2675      * @return The amount to preview next items when arrow srolling.
   2676      */
   2677     private int getArrowScrollPreviewLength() {
   2678         return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
   2679     }
   2680 
   2681     /**
   2682      * Determine how much we need to scroll in order to get the next selected view
   2683      * visible, with a fading edge showing below as applicable.  The amount is
   2684      * capped at {@link #getMaxScrollAmount()} .
   2685      *
   2686      * @param direction either {@link android.view.View#FOCUS_UP} or
   2687      *        {@link android.view.View#FOCUS_DOWN}.
   2688      * @param nextSelectedPosition The position of the next selection, or
   2689      *        {@link #INVALID_POSITION} if there is no next selectable position
   2690      * @return The amount to scroll. Note: this is always positive!  Direction
   2691      *         needs to be taken into account when actually scrolling.
   2692      */
   2693     private int amountToScroll(int direction, int nextSelectedPosition) {
   2694         final int listBottom = getHeight() - mListPadding.bottom;
   2695         final int listTop = mListPadding.top;
   2696 
   2697         int numChildren = getChildCount();
   2698 
   2699         if (direction == View.FOCUS_DOWN) {
   2700             int indexToMakeVisible = numChildren - 1;
   2701             if (nextSelectedPosition != INVALID_POSITION) {
   2702                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2703             }
   2704             while (numChildren <= indexToMakeVisible) {
   2705                 // Child to view is not attached yet.
   2706                 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
   2707                 numChildren++;
   2708             }
   2709             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2710             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2711 
   2712             int goalBottom = listBottom;
   2713             if (positionToMakeVisible < mItemCount - 1) {
   2714                 goalBottom -= getArrowScrollPreviewLength();
   2715             }
   2716 
   2717             if (viewToMakeVisible.getBottom() <= goalBottom) {
   2718                 // item is fully visible.
   2719                 return 0;
   2720             }
   2721 
   2722             if (nextSelectedPosition != INVALID_POSITION
   2723                     && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
   2724                 // item already has enough of it visible, changing selection is good enough
   2725                 return 0;
   2726             }
   2727 
   2728             int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
   2729 
   2730             if ((mFirstPosition + numChildren) == mItemCount) {
   2731                 // last is last in list -> make sure we don't scroll past it
   2732                 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
   2733                 amountToScroll = Math.min(amountToScroll, max);
   2734             }
   2735 
   2736             return Math.min(amountToScroll, getMaxScrollAmount());
   2737         } else {
   2738             int indexToMakeVisible = 0;
   2739             if (nextSelectedPosition != INVALID_POSITION) {
   2740                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2741             }
   2742             while (indexToMakeVisible < 0) {
   2743                 // Child to view is not attached yet.
   2744                 addViewAbove(getChildAt(0), mFirstPosition);
   2745                 mFirstPosition--;
   2746                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2747             }
   2748             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2749             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2750             int goalTop = listTop;
   2751             if (positionToMakeVisible > 0) {
   2752                 goalTop += getArrowScrollPreviewLength();
   2753             }
   2754             if (viewToMakeVisible.getTop() >= goalTop) {
   2755                 // item is fully visible.
   2756                 return 0;
   2757             }
   2758 
   2759             if (nextSelectedPosition != INVALID_POSITION &&
   2760                     (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
   2761                 // item already has enough of it visible, changing selection is good enough
   2762                 return 0;
   2763             }
   2764 
   2765             int amountToScroll = (goalTop - viewToMakeVisible.getTop());
   2766             if (mFirstPosition == 0) {
   2767                 // first is first in list -> make sure we don't scroll past it
   2768                 final int max = listTop - getChildAt(0).getTop();
   2769                 amountToScroll = Math.min(amountToScroll,  max);
   2770             }
   2771             return Math.min(amountToScroll, getMaxScrollAmount());
   2772         }
   2773     }
   2774 
   2775     /**
   2776      * Holds results of focus aware arrow scrolling.
   2777      */
   2778     static private class ArrowScrollFocusResult {
   2779         private int mSelectedPosition;
   2780         private int mAmountToScroll;
   2781 
   2782         /**
   2783          * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
   2784          */
   2785         void populate(int selectedPosition, int amountToScroll) {
   2786             mSelectedPosition = selectedPosition;
   2787             mAmountToScroll = amountToScroll;
   2788         }
   2789 
   2790         public int getSelectedPosition() {
   2791             return mSelectedPosition;
   2792         }
   2793 
   2794         public int getAmountToScroll() {
   2795             return mAmountToScroll;
   2796         }
   2797     }
   2798 
   2799     /**
   2800      * @param direction either {@link android.view.View#FOCUS_UP} or
   2801      *        {@link android.view.View#FOCUS_DOWN}.
   2802      * @return The position of the next selectable position of the views that
   2803      *         are currently visible, taking into account the fact that there might
   2804      *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
   2805      *         selectable view on screen in the given direction.
   2806      */
   2807     private int lookForSelectablePositionOnScreen(int direction) {
   2808         final int firstPosition = mFirstPosition;
   2809         if (direction == View.FOCUS_DOWN) {
   2810             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2811                     mSelectedPosition + 1 :
   2812                     firstPosition;
   2813             if (startPos >= mAdapter.getCount()) {
   2814                 return INVALID_POSITION;
   2815             }
   2816             if (startPos < firstPosition) {
   2817                 startPos = firstPosition;
   2818             }
   2819 
   2820             final int lastVisiblePos = getLastVisiblePosition();
   2821             final ListAdapter adapter = getAdapter();
   2822             for (int pos = startPos; pos <= lastVisiblePos; pos++) {
   2823                 if (adapter.isEnabled(pos)
   2824                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2825                     return pos;
   2826                 }
   2827             }
   2828         } else {
   2829             int last = firstPosition + getChildCount() - 1;
   2830             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2831                     mSelectedPosition - 1 :
   2832                     firstPosition + getChildCount() - 1;
   2833             if (startPos < 0 || startPos >= mAdapter.getCount()) {
   2834                 return INVALID_POSITION;
   2835             }
   2836             if (startPos > last) {
   2837                 startPos = last;
   2838             }
   2839 
   2840             final ListAdapter adapter = getAdapter();
   2841             for (int pos = startPos; pos >= firstPosition; pos--) {
   2842                 if (adapter.isEnabled(pos)
   2843                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2844                     return pos;
   2845                 }
   2846             }
   2847         }
   2848         return INVALID_POSITION;
   2849     }
   2850 
   2851     /**
   2852      * Do an arrow scroll based on focus searching.  If a new view is
   2853      * given focus, return the selection delta and amount to scroll via
   2854      * an {@link ArrowScrollFocusResult}, otherwise, return null.
   2855      *
   2856      * @param direction either {@link android.view.View#FOCUS_UP} or
   2857      *        {@link android.view.View#FOCUS_DOWN}.
   2858      * @return The result if focus has changed, or <code>null</code>.
   2859      */
   2860     private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
   2861         final View selectedView = getSelectedView();
   2862         View newFocus;
   2863         if (selectedView != null && selectedView.hasFocus()) {
   2864             View oldFocus = selectedView.findFocus();
   2865             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
   2866         } else {
   2867             if (direction == View.FOCUS_DOWN) {
   2868                 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
   2869                 final int listTop = mListPadding.top +
   2870                         (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2871                 final int ySearchPoint =
   2872                         (selectedView != null && selectedView.getTop() > listTop) ?
   2873                                 selectedView.getTop() :
   2874                                 listTop;
   2875                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2876             } else {
   2877                 final boolean bottomFadingEdgeShowing =
   2878                         (mFirstPosition + getChildCount() - 1) < mItemCount;
   2879                 final int listBottom = getHeight() - mListPadding.bottom -
   2880                         (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2881                 final int ySearchPoint =
   2882                         (selectedView != null && selectedView.getBottom() < listBottom) ?
   2883                                 selectedView.getBottom() :
   2884                                 listBottom;
   2885                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2886             }
   2887             newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
   2888         }
   2889 
   2890         if (newFocus != null) {
   2891             final int positionOfNewFocus = positionOfNewFocus(newFocus);
   2892 
   2893             // if the focus change is in a different new position, make sure
   2894             // we aren't jumping over another selectable position
   2895             if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
   2896                 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
   2897                 if (selectablePosition != INVALID_POSITION &&
   2898                         ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
   2899                         (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
   2900                     return null;
   2901                 }
   2902             }
   2903 
   2904             int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
   2905 
   2906             final int maxScrollAmount = getMaxScrollAmount();
   2907             if (focusScroll < maxScrollAmount) {
   2908                 // not moving too far, safe to give next view focus
   2909                 newFocus.requestFocus(direction);
   2910                 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
   2911                 return mArrowScrollFocusResult;
   2912             } else if (distanceToView(newFocus) < maxScrollAmount){
   2913                 // Case to consider:
   2914                 // too far to get entire next focusable on screen, but by going
   2915                 // max scroll amount, we are getting it at least partially in view,
   2916                 // so give it focus and scroll the max ammount.
   2917                 newFocus.requestFocus(direction);
   2918                 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
   2919                 return mArrowScrollFocusResult;
   2920             }
   2921         }
   2922         return null;
   2923     }
   2924 
   2925     /**
   2926      * @param newFocus The view that would have focus.
   2927      * @return the position that contains newFocus
   2928      */
   2929     private int positionOfNewFocus(View newFocus) {
   2930         final int numChildren = getChildCount();
   2931         for (int i = 0; i < numChildren; i++) {
   2932             final View child = getChildAt(i);
   2933             if (isViewAncestorOf(newFocus, child)) {
   2934                 return mFirstPosition + i;
   2935             }
   2936         }
   2937         throw new IllegalArgumentException("newFocus is not a child of any of the"
   2938                 + " children of the list!");
   2939     }
   2940 
   2941     /**
   2942      * Return true if child is an ancestor of parent, (or equal to the parent).
   2943      */
   2944     private boolean isViewAncestorOf(View child, View parent) {
   2945         if (child == parent) {
   2946             return true;
   2947         }
   2948 
   2949         final ViewParent theParent = child.getParent();
   2950         return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
   2951     }
   2952 
   2953     /**
   2954      * Determine how much we need to scroll in order to get newFocus in view.
   2955      * @param direction either {@link android.view.View#FOCUS_UP} or
   2956      *        {@link android.view.View#FOCUS_DOWN}.
   2957      * @param newFocus The view that would take focus.
   2958      * @param positionOfNewFocus The position of the list item containing newFocus
   2959      * @return The amount to scroll.  Note: this is always positive!  Direction
   2960      *   needs to be taken into account when actually scrolling.
   2961      */
   2962     private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
   2963         int amountToScroll = 0;
   2964         newFocus.getDrawingRect(mTempRect);
   2965         offsetDescendantRectToMyCoords(newFocus, mTempRect);
   2966         if (direction == View.FOCUS_UP) {
   2967             if (mTempRect.top < mListPadding.top) {
   2968                 amountToScroll = mListPadding.top - mTempRect.top;
   2969                 if (positionOfNewFocus > 0) {
   2970                     amountToScroll += getArrowScrollPreviewLength();
   2971                 }
   2972             }
   2973         } else {
   2974             final int listBottom = getHeight() - mListPadding.bottom;
   2975             if (mTempRect.bottom > listBottom) {
   2976                 amountToScroll = mTempRect.bottom - listBottom;
   2977                 if (positionOfNewFocus < mItemCount - 1) {
   2978                     amountToScroll += getArrowScrollPreviewLength();
   2979                 }
   2980             }
   2981         }
   2982         return amountToScroll;
   2983     }
   2984 
   2985     /**
   2986      * Determine the distance to the nearest edge of a view in a particular
   2987      * direction.
   2988      *
   2989      * @param descendant A descendant of this list.
   2990      * @return The distance, or 0 if the nearest edge is already on screen.
   2991      */
   2992     private int distanceToView(View descendant) {
   2993         int distance = 0;
   2994         descendant.getDrawingRect(mTempRect);
   2995         offsetDescendantRectToMyCoords(descendant, mTempRect);
   2996         final int listBottom = mBottom - mTop - mListPadding.bottom;
   2997         if (mTempRect.bottom < mListPadding.top) {
   2998             distance = mListPadding.top - mTempRect.bottom;
   2999         } else if (mTempRect.top > listBottom) {
   3000             distance = mTempRect.top - listBottom;
   3001         }
   3002         return distance;
   3003     }
   3004 
   3005 
   3006     /**
   3007      * Scroll the children by amount, adding a view at the end and removing
   3008      * views that fall off as necessary.
   3009      *
   3010      * @param amount The amount (positive or negative) to scroll.
   3011      */
   3012     private void scrollListItemsBy(int amount) {
   3013         offsetChildrenTopAndBottom(amount);
   3014 
   3015         final int listBottom = getHeight() - mListPadding.bottom;
   3016         final int listTop = mListPadding.top;
   3017         final AbsListView.RecycleBin recycleBin = mRecycler;
   3018 
   3019         if (amount < 0) {
   3020             // shifted items up
   3021 
   3022             // may need to pan views into the bottom space
   3023             int numChildren = getChildCount();
   3024             View last = getChildAt(numChildren - 1);
   3025             while (last.getBottom() < listBottom) {
   3026                 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
   3027                 if (lastVisiblePosition < mItemCount - 1) {
   3028                     last = addViewBelow(last, lastVisiblePosition);
   3029                     numChildren++;
   3030                 } else {
   3031                     break;
   3032                 }
   3033             }
   3034 
   3035             // may have brought in the last child of the list that is skinnier
   3036             // than the fading edge, thereby leaving space at the end.  need
   3037             // to shift back
   3038             if (last.getBottom() < listBottom) {
   3039                 offsetChildrenTopAndBottom(listBottom - last.getBottom());
   3040             }
   3041 
   3042             // top views may be panned off screen
   3043             View first = getChildAt(0);
   3044             while (first.getBottom() < listTop) {
   3045                 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
   3046                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   3047                     recycleBin.addScrapView(first, mFirstPosition);
   3048                 }
   3049                 detachViewFromParent(first);
   3050                 first = getChildAt(0);
   3051                 mFirstPosition++;
   3052             }
   3053         } else {
   3054             // shifted items down
   3055             View first = getChildAt(0);
   3056 
   3057             // may need to pan views into top
   3058             while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
   3059                 first = addViewAbove(first, mFirstPosition);
   3060                 mFirstPosition--;
   3061             }
   3062 
   3063             // may have brought the very first child of the list in too far and
   3064             // need to shift it back
   3065             if (first.getTop() > listTop) {
   3066                 offsetChildrenTopAndBottom(listTop - first.getTop());
   3067             }
   3068 
   3069             int lastIndex = getChildCount() - 1;
   3070             View last = getChildAt(lastIndex);
   3071 
   3072             // bottom view may be panned off screen
   3073             while (last.getTop() > listBottom) {
   3074                 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
   3075                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   3076                     recycleBin.addScrapView(last, mFirstPosition+lastIndex);
   3077                 }
   3078                 detachViewFromParent(last);
   3079                 last = getChildAt(--lastIndex);
   3080             }
   3081         }
   3082     }
   3083 
   3084     private View addViewAbove(View theView, int position) {
   3085         int abovePosition = position - 1;
   3086         View view = obtainView(abovePosition, mIsScrap);
   3087         int edgeOfNewChild = theView.getTop() - mDividerHeight;
   3088         setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
   3089                 false, mIsScrap[0]);
   3090         return view;
   3091     }
   3092 
   3093     private View addViewBelow(View theView, int position) {
   3094         int belowPosition = position + 1;
   3095         View view = obtainView(belowPosition, mIsScrap);
   3096         int edgeOfNewChild = theView.getBottom() + mDividerHeight;
   3097         setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
   3098                 false, mIsScrap[0]);
   3099         return view;
   3100     }
   3101 
   3102     /**
   3103      * Indicates that the views created by the ListAdapter can contain focusable
   3104      * items.
   3105      *
   3106      * @param itemsCanFocus true if items can get focus, false otherwise
   3107      */
   3108     public void setItemsCanFocus(boolean itemsCanFocus) {
   3109         mItemsCanFocus = itemsCanFocus;
   3110         if (!itemsCanFocus) {
   3111             setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
   3112         }
   3113     }
   3114 
   3115     /**
   3116      * @return Whether the views created by the ListAdapter can contain focusable
   3117      * items.
   3118      */
   3119     public boolean getItemsCanFocus() {
   3120         return mItemsCanFocus;
   3121     }
   3122 
   3123     @Override
   3124     public boolean isOpaque() {
   3125         boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
   3126                 hasOpaqueScrollbars()) || super.isOpaque();
   3127         if (retValue) {
   3128             // only return true if the list items cover the entire area of the view
   3129             final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
   3130             View first = getChildAt(0);
   3131             if (first == null || first.getTop() > listTop) {
   3132                 return false;
   3133             }
   3134             final int listBottom = getHeight() -
   3135                     (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
   3136             View last = getChildAt(getChildCount() - 1);
   3137             if (last == null || last.getBottom() < listBottom) {
   3138                 return false;
   3139             }
   3140         }
   3141         return retValue;
   3142     }
   3143 
   3144     @Override
   3145     public void setCacheColorHint(int color) {
   3146         final boolean opaque = (color >>> 24) == 0xFF;
   3147         mIsCacheColorOpaque = opaque;
   3148         if (opaque) {
   3149             if (mDividerPaint == null) {
   3150                 mDividerPaint = new Paint();
   3151             }
   3152             mDividerPaint.setColor(color);
   3153         }
   3154         super.setCacheColorHint(color);
   3155     }
   3156 
   3157     void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
   3158         final int height = drawable.getMinimumHeight();
   3159 
   3160         canvas.save();
   3161         canvas.clipRect(bounds);
   3162 
   3163         final int span = bounds.bottom - bounds.top;
   3164         if (span < height) {
   3165             bounds.top = bounds.bottom - height;
   3166         }
   3167 
   3168         drawable.setBounds(bounds);
   3169         drawable.draw(canvas);
   3170 
   3171         canvas.restore();
   3172     }
   3173 
   3174     void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
   3175         final int height = drawable.getMinimumHeight();
   3176 
   3177         canvas.save();
   3178         canvas.clipRect(bounds);
   3179 
   3180         final int span = bounds.bottom - bounds.top;
   3181         if (span < height) {
   3182             bounds.bottom = bounds.top + height;
   3183         }
   3184 
   3185         drawable.setBounds(bounds);
   3186         drawable.draw(canvas);
   3187 
   3188         canvas.restore();
   3189     }
   3190 
   3191     @Override
   3192     protected void dispatchDraw(Canvas canvas) {
   3193         if (mCachingStarted) {
   3194             mCachingActive = true;
   3195         }
   3196 
   3197         // Draw the dividers
   3198         final int dividerHeight = mDividerHeight;
   3199         final Drawable overscrollHeader = mOverScrollHeader;
   3200         final Drawable overscrollFooter = mOverScrollFooter;
   3201         final boolean drawOverscrollHeader = overscrollHeader != null;
   3202         final boolean drawOverscrollFooter = overscrollFooter != null;
   3203         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
   3204 
   3205         if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
   3206             // Only modify the top and bottom in the loop, we set the left and right here
   3207             final Rect bounds = mTempRect;
   3208             bounds.left = mPaddingLeft;
   3209             bounds.right = mRight - mLeft - mPaddingRight;
   3210 
   3211             final int count = getChildCount();
   3212             final int headerCount = mHeaderViewInfos.size();
   3213             final int itemCount = mItemCount;
   3214             final int footerLimit = (itemCount - mFooterViewInfos.size());
   3215             final boolean headerDividers = mHeaderDividersEnabled;
   3216             final boolean footerDividers = mFooterDividersEnabled;
   3217             final int first = mFirstPosition;
   3218             final boolean areAllItemsSelectable = mAreAllItemsSelectable;
   3219             final ListAdapter adapter = mAdapter;
   3220             // If the list is opaque *and* the background is not, we want to
   3221             // fill a rect where the dividers would be for non-selectable items
   3222             // If the list is opaque and the background is also opaque, we don't
   3223             // need to draw anything since the background will do it for us
   3224             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
   3225 
   3226             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
   3227                 mDividerPaint = new Paint();
   3228                 mDividerPaint.setColor(getCacheColorHint());
   3229             }
   3230             final Paint paint = mDividerPaint;
   3231 
   3232             int effectivePaddingTop = 0;
   3233             int effectivePaddingBottom = 0;
   3234             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   3235                 effectivePaddingTop = mListPadding.top;
   3236                 effectivePaddingBottom = mListPadding.bottom;
   3237             }
   3238 
   3239             final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
   3240             if (!mStackFromBottom) {
   3241                 int bottom = 0;
   3242 
   3243                 // Draw top divider or header for overscroll
   3244                 final int scrollY = mScrollY;
   3245                 if (count > 0 && scrollY < 0) {
   3246                     if (drawOverscrollHeader) {
   3247                         bounds.bottom = 0;
   3248                         bounds.top = scrollY;
   3249                         drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3250                     } else if (drawDividers) {
   3251                         bounds.bottom = 0;
   3252                         bounds.top = -dividerHeight;
   3253                         drawDivider(canvas, bounds, -1);
   3254                     }
   3255                 }
   3256 
   3257                 for (int i = 0; i < count; i++) {
   3258                     final int itemIndex = (first + i);
   3259                     final boolean isHeader = (itemIndex < headerCount);
   3260                     final boolean isFooter = (itemIndex >= footerLimit);
   3261                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
   3262                         final View child = getChildAt(i);
   3263                         bottom = child.getBottom();
   3264                         final boolean isLastItem = (i == (count - 1));
   3265 
   3266                         if (drawDividers && (bottom < listBottom)
   3267                                 && !(drawOverscrollFooter && isLastItem)) {
   3268                             final int nextIndex = (itemIndex + 1);
   3269                             // Draw dividers between enabled items, headers and/or
   3270                             // footers when enabled, and the end of the list.
   3271                             if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex)
   3272                                     || (headerDividers && isHeader)
   3273                                     || (footerDividers && isFooter)) && (isLastItem
   3274                                     || adapter.isEnabled(nextIndex)
   3275                                     || (headerDividers && (nextIndex < headerCount))
   3276                                     || (footerDividers && (nextIndex >= footerLimit))))) {
   3277                                 bounds.top = bottom;
   3278                                 bounds.bottom = bottom + dividerHeight;
   3279                                 drawDivider(canvas, bounds, i);
   3280                             } else if (fillForMissingDividers) {
   3281                                 bounds.top = bottom;
   3282                                 bounds.bottom = bottom + dividerHeight;
   3283                                 canvas.drawRect(bounds, paint);
   3284                             }
   3285                         }
   3286                     }
   3287                 }
   3288 
   3289                 final int overFooterBottom = mBottom + mScrollY;
   3290                 if (drawOverscrollFooter && first + count == itemCount &&
   3291                         overFooterBottom > bottom) {
   3292                     bounds.top = bottom;
   3293                     bounds.bottom = overFooterBottom;
   3294                     drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3295                 }
   3296             } else {
   3297                 int top;
   3298 
   3299                 final int scrollY = mScrollY;
   3300 
   3301                 if (count > 0 && drawOverscrollHeader) {
   3302                     bounds.top = scrollY;
   3303                     bounds.bottom = getChildAt(0).getTop();
   3304                     drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3305                 }
   3306 
   3307                 final int start = drawOverscrollHeader ? 1 : 0;
   3308                 for (int i = start; i < count; i++) {
   3309                     final int itemIndex = (first + i);
   3310                     final boolean isHeader = (itemIndex < headerCount);
   3311                     final boolean isFooter = (itemIndex >= footerLimit);
   3312                     if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
   3313                         final View child = getChildAt(i);
   3314                         top = child.getTop();
   3315                         if (drawDividers && (top > effectivePaddingTop)) {
   3316                             final boolean isFirstItem = (i == start);
   3317                             final int previousIndex = (itemIndex - 1);
   3318                             // Draw dividers between enabled items, headers and/or
   3319                             // footers when enabled, and the end of the list.
   3320                             if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex)
   3321                                     || (headerDividers && isHeader)
   3322                                     || (footerDividers && isFooter)) && (isFirstItem
   3323                                     || adapter.isEnabled(previousIndex)
   3324                                     || (headerDividers && (previousIndex < headerCount))
   3325                                     || (footerDividers && (previousIndex >= footerLimit))))) {
   3326                                 bounds.top = top - dividerHeight;
   3327                                 bounds.bottom = top;
   3328                                 // Give the method the child ABOVE the divider,
   3329                                 // so we subtract one from our child position.
   3330                                 // Give -1 when there is no child above the
   3331                                 // divider.
   3332                                 drawDivider(canvas, bounds, i - 1);
   3333                             } else if (fillForMissingDividers) {
   3334                                 bounds.top = top - dividerHeight;
   3335                                 bounds.bottom = top;
   3336                                 canvas.drawRect(bounds, paint);
   3337                             }
   3338                         }
   3339                     }
   3340                 }
   3341 
   3342                 if (count > 0 && scrollY > 0) {
   3343                     if (drawOverscrollFooter) {
   3344                         final int absListBottom = mBottom;
   3345                         bounds.top = absListBottom;
   3346                         bounds.bottom = absListBottom + scrollY;
   3347                         drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3348                     } else if (drawDividers) {
   3349                         bounds.top = listBottom;
   3350                         bounds.bottom = listBottom + dividerHeight;
   3351                         drawDivider(canvas, bounds, -1);
   3352                     }
   3353                 }
   3354             }
   3355         }
   3356 
   3357         // Draw the indicators (these should be drawn above the dividers) and children
   3358         super.dispatchDraw(canvas);
   3359     }
   3360 
   3361     @Override
   3362     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   3363         boolean more = super.drawChild(canvas, child, drawingTime);
   3364         if (mCachingActive && child.mCachingFailed) {
   3365             mCachingActive = false;
   3366         }
   3367         return more;
   3368     }
   3369 
   3370     /**
   3371      * Draws a divider for the given child in the given bounds.
   3372      *
   3373      * @param canvas The canvas to draw to.
   3374      * @param bounds The bounds of the divider.
   3375      * @param childIndex The index of child (of the View) above the divider.
   3376      *            This will be -1 if there is no child above the divider to be
   3377      *            drawn.
   3378      */
   3379     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
   3380         // This widget draws the same divider for all children
   3381         final Drawable divider = mDivider;
   3382 
   3383         divider.setBounds(bounds);
   3384         divider.draw(canvas);
   3385     }
   3386 
   3387     /**
   3388      * Returns the drawable that will be drawn between each item in the list.
   3389      *
   3390      * @return the current drawable drawn between list elements
   3391      */
   3392     public Drawable getDivider() {
   3393         return mDivider;
   3394     }
   3395 
   3396     /**
   3397      * Sets the drawable that will be drawn between each item in the list. If the drawable does
   3398      * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
   3399      *
   3400      * @param divider The drawable to use.
   3401      */
   3402     public void setDivider(Drawable divider) {
   3403         if (divider != null) {
   3404             mDividerHeight = divider.getIntrinsicHeight();
   3405         } else {
   3406             mDividerHeight = 0;
   3407         }
   3408         mDivider = divider;
   3409         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
   3410         requestLayout();
   3411         invalidate();
   3412     }
   3413 
   3414     /**
   3415      * @return Returns the height of the divider that will be drawn between each item in the list.
   3416      */
   3417     public int getDividerHeight() {
   3418         return mDividerHeight;
   3419     }
   3420 
   3421     /**
   3422      * Sets the height of the divider that will be drawn between each item in the list. Calling
   3423      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
   3424      *
   3425      * @param height The new height of the divider in pixels.
   3426      */
   3427     public void setDividerHeight(int height) {
   3428         mDividerHeight = height;
   3429         requestLayout();
   3430         invalidate();
   3431     }
   3432 
   3433     /**
   3434      * Enables or disables the drawing of the divider for header views.
   3435      *
   3436      * @param headerDividersEnabled True to draw the headers, false otherwise.
   3437      *
   3438      * @see #setFooterDividersEnabled(boolean)
   3439      * @see #areHeaderDividersEnabled()
   3440      * @see #addHeaderView(android.view.View)
   3441      */
   3442     public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
   3443         mHeaderDividersEnabled = headerDividersEnabled;
   3444         invalidate();
   3445     }
   3446 
   3447     /**
   3448      * @return Whether the drawing of the divider for header views is enabled
   3449      *
   3450      * @see #setHeaderDividersEnabled(boolean)
   3451      */
   3452     public boolean areHeaderDividersEnabled() {
   3453         return mHeaderDividersEnabled;
   3454     }
   3455 
   3456     /**
   3457      * Enables or disables the drawing of the divider for footer views.
   3458      *
   3459      * @param footerDividersEnabled True to draw the footers, false otherwise.
   3460      *
   3461      * @see #setHeaderDividersEnabled(boolean)
   3462      * @see #areFooterDividersEnabled()
   3463      * @see #addFooterView(android.view.View)
   3464      */
   3465     public void setFooterDividersEnabled(boolean footerDividersEnabled) {
   3466         mFooterDividersEnabled = footerDividersEnabled;
   3467         invalidate();
   3468     }
   3469 
   3470     /**
   3471      * @return Whether the drawing of the divider for footer views is enabled
   3472      *
   3473      * @see #setFooterDividersEnabled(boolean)
   3474      */
   3475     public boolean areFooterDividersEnabled() {
   3476         return mFooterDividersEnabled;
   3477     }
   3478 
   3479     /**
   3480      * Sets the drawable that will be drawn above all other list content.
   3481      * This area can become visible when the user overscrolls the list.
   3482      *
   3483      * @param header The drawable to use
   3484      */
   3485     public void setOverscrollHeader(Drawable header) {
   3486         mOverScrollHeader = header;
   3487         if (mScrollY < 0) {
   3488             invalidate();
   3489         }
   3490     }
   3491 
   3492     /**
   3493      * @return The drawable that will be drawn above all other list content
   3494      */
   3495     public Drawable getOverscrollHeader() {
   3496         return mOverScrollHeader;
   3497     }
   3498 
   3499     /**
   3500      * Sets the drawable that will be drawn below all other list content.
   3501      * This area can become visible when the user overscrolls the list,
   3502      * or when the list's content does not fully fill the container area.
   3503      *
   3504      * @param footer The drawable to use
   3505      */
   3506     public void setOverscrollFooter(Drawable footer) {
   3507         mOverScrollFooter = footer;
   3508         invalidate();
   3509     }
   3510 
   3511     /**
   3512      * @return The drawable that will be drawn below all other list content
   3513      */
   3514     public Drawable getOverscrollFooter() {
   3515         return mOverScrollFooter;
   3516     }
   3517 
   3518     @Override
   3519     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   3520         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   3521 
   3522         final ListAdapter adapter = mAdapter;
   3523         int closetChildIndex = -1;
   3524         int closestChildTop = 0;
   3525         if (adapter != null && gainFocus && previouslyFocusedRect != null) {
   3526             previouslyFocusedRect.offset(mScrollX, mScrollY);
   3527 
   3528             // Don't cache the result of getChildCount or mFirstPosition here,
   3529             // it could change in layoutChildren.
   3530             if (adapter.getCount() < getChildCount() + mFirstPosition) {
   3531                 mLayoutMode = LAYOUT_NORMAL;
   3532                 layoutChildren();
   3533             }
   3534 
   3535             // figure out which item should be selected based on previously
   3536             // focused rect
   3537             Rect otherRect = mTempRect;
   3538             int minDistance = Integer.MAX_VALUE;
   3539             final int childCount = getChildCount();
   3540             final int firstPosition = mFirstPosition;
   3541 
   3542             for (int i = 0; i < childCount; i++) {
   3543                 // only consider selectable views
   3544                 if (!adapter.isEnabled(firstPosition + i)) {
   3545                     continue;
   3546                 }
   3547 
   3548                 View other = getChildAt(i);
   3549                 other.getDrawingRect(otherRect);
   3550                 offsetDescendantRectToMyCoords(other, otherRect);
   3551                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
   3552 
   3553                 if (distance < minDistance) {
   3554                     minDistance = distance;
   3555                     closetChildIndex = i;
   3556                     closestChildTop = other.getTop();
   3557                 }
   3558             }
   3559         }
   3560 
   3561         if (closetChildIndex >= 0) {
   3562             setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
   3563         } else {
   3564             requestLayout();
   3565         }
   3566     }
   3567 
   3568 
   3569     /*
   3570      * (non-Javadoc)
   3571      *
   3572      * Children specified in XML are assumed to be header views. After we have
   3573      * parsed them move them out of the children list and into mHeaderViews.
   3574      */
   3575     @Override
   3576     protected void onFinishInflate() {
   3577         super.onFinishInflate();
   3578 
   3579         int count = getChildCount();
   3580         if (count > 0) {
   3581             for (int i = 0; i < count; ++i) {
   3582                 addHeaderView(getChildAt(i));
   3583             }
   3584             removeAllViews();
   3585         }
   3586     }
   3587 
   3588     /* (non-Javadoc)
   3589      * @see android.view.View#findViewById(int)
   3590      * First look in our children, then in any header and footer views that may be scrolled off.
   3591      */
   3592     @Override
   3593     protected View findViewTraversal(int id) {
   3594         View v;
   3595         v = super.findViewTraversal(id);
   3596         if (v == null) {
   3597             v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
   3598             if (v != null) {
   3599                 return v;
   3600             }
   3601             v = findViewInHeadersOrFooters(mFooterViewInfos, id);
   3602             if (v != null) {
   3603                 return v;
   3604             }
   3605         }
   3606         return v;
   3607     }
   3608 
   3609     /* (non-Javadoc)
   3610      *
   3611      * Look in the passed in list of headers or footers for the view.
   3612      */
   3613     View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
   3614         if (where != null) {
   3615             int len = where.size();
   3616             View v;
   3617 
   3618             for (int i = 0; i < len; i++) {
   3619                 v = where.get(i).view;
   3620 
   3621                 if (!v.isRootNamespace()) {
   3622                     v = v.findViewById(id);
   3623 
   3624                     if (v != null) {
   3625                         return v;
   3626                     }
   3627                 }
   3628             }
   3629         }
   3630         return null;
   3631     }
   3632 
   3633     /* (non-Javadoc)
   3634      * @see android.view.View#findViewWithTag(Object)
   3635      * First look in our children, then in any header and footer views that may be scrolled off.
   3636      */
   3637     @Override
   3638     protected View findViewWithTagTraversal(Object tag) {
   3639         View v;
   3640         v = super.findViewWithTagTraversal(tag);
   3641         if (v == null) {
   3642             v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
   3643             if (v != null) {
   3644                 return v;
   3645             }
   3646 
   3647             v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
   3648             if (v != null) {
   3649                 return v;
   3650             }
   3651         }
   3652         return v;
   3653     }
   3654 
   3655     /* (non-Javadoc)
   3656      *
   3657      * Look in the passed in list of headers or footers for the view with the tag.
   3658      */
   3659     View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
   3660         if (where != null) {
   3661             int len = where.size();
   3662             View v;
   3663 
   3664             for (int i = 0; i < len; i++) {
   3665                 v = where.get(i).view;
   3666 
   3667                 if (!v.isRootNamespace()) {
   3668                     v = v.findViewWithTag(tag);
   3669 
   3670                     if (v != null) {
   3671                         return v;
   3672                     }
   3673                 }
   3674             }
   3675         }
   3676         return null;
   3677     }
   3678 
   3679     /**
   3680      * @hide
   3681      * @see android.view.View#findViewByPredicate(Predicate)
   3682      * First look in our children, then in any header and footer views that may be scrolled off.
   3683      */
   3684     @Override
   3685     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
   3686         View v;
   3687         v = super.findViewByPredicateTraversal(predicate, childToSkip);
   3688         if (v == null) {
   3689             v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
   3690             if (v != null) {
   3691                 return v;
   3692             }
   3693 
   3694             v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
   3695             if (v != null) {
   3696                 return v;
   3697             }
   3698         }
   3699         return v;
   3700     }
   3701 
   3702     /* (non-Javadoc)
   3703      *
   3704      * Look in the passed in list of headers or footers for the first view that matches
   3705      * the predicate.
   3706      */
   3707     View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
   3708             Predicate<View> predicate, View childToSkip) {
   3709         if (where != null) {
   3710             int len = where.size();
   3711             View v;
   3712 
   3713             for (int i = 0; i < len; i++) {
   3714                 v = where.get(i).view;
   3715 
   3716                 if (v != childToSkip && !v.isRootNamespace()) {
   3717                     v = v.findViewByPredicate(predicate);
   3718 
   3719                     if (v != null) {
   3720                         return v;
   3721                     }
   3722                 }
   3723             }
   3724         }
   3725         return null;
   3726     }
   3727 
   3728     /**
   3729      * Returns the set of checked items ids. The result is only valid if the
   3730      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
   3731      *
   3732      * @return A new array which contains the id of each checked item in the
   3733      *         list.
   3734      *
   3735      * @deprecated Use {@link #getCheckedItemIds()} instead.
   3736      */
   3737     @Deprecated
   3738     public long[] getCheckItemIds() {
   3739         // Use new behavior that correctly handles stable ID mapping.
   3740         if (mAdapter != null && mAdapter.hasStableIds()) {
   3741             return getCheckedItemIds();
   3742         }
   3743 
   3744         // Old behavior was buggy, but would sort of work for adapters without stable IDs.
   3745         // Fall back to it to support legacy apps.
   3746         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
   3747             final SparseBooleanArray states = mCheckStates;
   3748             final int count = states.size();
   3749             final long[] ids = new long[count];
   3750             final ListAdapter adapter = mAdapter;
   3751 
   3752             int checkedCount = 0;
   3753             for (int i = 0; i < count; i++) {
   3754                 if (states.valueAt(i)) {
   3755                     ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
   3756                 }
   3757             }
   3758 
   3759             // Trim array if needed. mCheckStates may contain false values
   3760             // resulting in checkedCount being smaller than count.
   3761             if (checkedCount == count) {
   3762                 return ids;
   3763             } else {
   3764                 final long[] result = new long[checkedCount];
   3765                 System.arraycopy(ids, 0, result, 0, checkedCount);
   3766 
   3767                 return result;
   3768             }
   3769         }
   3770         return new long[0];
   3771     }
   3772 
   3773     @Override
   3774     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   3775         super.onInitializeAccessibilityEvent(event);
   3776         event.setClassName(ListView.class.getName());
   3777     }
   3778 
   3779     @Override
   3780     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   3781         super.onInitializeAccessibilityNodeInfo(info);
   3782         info.setClassName(ListView.class.getName());
   3783 
   3784         final int count = getCount();
   3785         final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false);
   3786         info.setCollectionInfo(collectionInfo);
   3787     }
   3788 
   3789     @Override
   3790     public void onInitializeAccessibilityNodeInfoForItem(
   3791             View view, int position, AccessibilityNodeInfo info) {
   3792         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
   3793 
   3794         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
   3795         final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
   3796         final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(0, 1, position, 1, isHeading);
   3797         info.setCollectionItemInfo(itemInfo);
   3798     }
   3799 }
   3800