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