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