ListView.java revision 4602a709d83fe0886f538256045f113d15feedaa
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            if (position < 0 || position >= count) {
2078                return INVALID_POSITION;
2079            }
2080            return position;
2081        } else {
2082            if (position < 0 || position >= count) {
2083                return INVALID_POSITION;
2084            }
2085            return position;
2086        }
2087    }
2088
2089    /**
2090     * setSelectionAfterHeaderView set the selection to be the first list item
2091     * after the header views.
2092     */
2093    public void setSelectionAfterHeaderView() {
2094        final int count = mHeaderViewInfos.size();
2095        if (count > 0) {
2096            mNextSelectedPosition = 0;
2097            return;
2098        }
2099
2100        if (mAdapter != null) {
2101            setSelection(count);
2102        } else {
2103            mNextSelectedPosition = count;
2104            mLayoutMode = LAYOUT_SET_SELECTION;
2105        }
2106
2107    }
2108
2109    @Override
2110    public boolean dispatchKeyEvent(KeyEvent event) {
2111        // Dispatch in the normal way
2112        boolean handled = super.dispatchKeyEvent(event);
2113        if (!handled) {
2114            // If we didn't handle it...
2115            View focused = getFocusedChild();
2116            if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2117                // ... and our focused child didn't handle it
2118                // ... give it to ourselves so we can scroll if necessary
2119                handled = onKeyDown(event.getKeyCode(), event);
2120            }
2121        }
2122        return handled;
2123    }
2124
2125    @Override
2126    public boolean onKeyDown(int keyCode, KeyEvent event) {
2127        return commonKey(keyCode, 1, event);
2128    }
2129
2130    @Override
2131    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2132        return commonKey(keyCode, repeatCount, event);
2133    }
2134
2135    @Override
2136    public boolean onKeyUp(int keyCode, KeyEvent event) {
2137        return commonKey(keyCode, 1, event);
2138    }
2139
2140    private boolean commonKey(int keyCode, int count, KeyEvent event) {
2141        if (mAdapter == null || !mIsAttached) {
2142            return false;
2143        }
2144
2145        if (mDataChanged) {
2146            layoutChildren();
2147        }
2148
2149        boolean handled = false;
2150        int action = event.getAction();
2151
2152        if (action != KeyEvent.ACTION_UP) {
2153            switch (keyCode) {
2154            case KeyEvent.KEYCODE_DPAD_UP:
2155                if (event.hasNoModifiers()) {
2156                    handled = resurrectSelectionIfNeeded();
2157                    if (!handled) {
2158                        while (count-- > 0) {
2159                            if (arrowScroll(FOCUS_UP)) {
2160                                handled = true;
2161                            } else {
2162                                break;
2163                            }
2164                        }
2165                    }
2166                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2167                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2168                }
2169                break;
2170
2171            case KeyEvent.KEYCODE_DPAD_DOWN:
2172                if (event.hasNoModifiers()) {
2173                    handled = resurrectSelectionIfNeeded();
2174                    if (!handled) {
2175                        while (count-- > 0) {
2176                            if (arrowScroll(FOCUS_DOWN)) {
2177                                handled = true;
2178                            } else {
2179                                break;
2180                            }
2181                        }
2182                    }
2183                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2184                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2185                }
2186                break;
2187
2188            case KeyEvent.KEYCODE_DPAD_LEFT:
2189                if (event.hasNoModifiers()) {
2190                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2191                }
2192                break;
2193
2194            case KeyEvent.KEYCODE_DPAD_RIGHT:
2195                if (event.hasNoModifiers()) {
2196                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2197                }
2198                break;
2199
2200            case KeyEvent.KEYCODE_DPAD_CENTER:
2201            case KeyEvent.KEYCODE_ENTER:
2202                if (event.hasNoModifiers()) {
2203                    handled = resurrectSelectionIfNeeded();
2204                    if (!handled
2205                            && event.getRepeatCount() == 0 && getChildCount() > 0) {
2206                        keyPressed();
2207                        handled = true;
2208                    }
2209                }
2210                break;
2211
2212            case KeyEvent.KEYCODE_SPACE:
2213                if (mPopup == null || !mPopup.isShowing()) {
2214                    if (event.hasNoModifiers()) {
2215                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
2216                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2217                        handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
2218                    }
2219                    handled = true;
2220                }
2221                break;
2222
2223            case KeyEvent.KEYCODE_PAGE_UP:
2224                if (event.hasNoModifiers()) {
2225                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
2226                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2227                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2228                }
2229                break;
2230
2231            case KeyEvent.KEYCODE_PAGE_DOWN:
2232                if (event.hasNoModifiers()) {
2233                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
2234                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2235                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2236                }
2237                break;
2238
2239            case KeyEvent.KEYCODE_MOVE_HOME:
2240                if (event.hasNoModifiers()) {
2241                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2242                }
2243                break;
2244
2245            case KeyEvent.KEYCODE_MOVE_END:
2246                if (event.hasNoModifiers()) {
2247                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2248                }
2249                break;
2250
2251            case KeyEvent.KEYCODE_TAB:
2252                // XXX Sometimes it is useful to be able to TAB through the items in
2253                //     a ListView sequentially.  Unfortunately this can create an
2254                //     asymmetry in TAB navigation order unless the list selection
2255                //     always reverts to the top or bottom when receiving TAB focus from
2256                //     another widget.  Leaving this behavior disabled for now but
2257                //     perhaps it should be configurable (and more comprehensive).
2258                if (false) {
2259                    if (event.hasNoModifiers()) {
2260                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2261                    } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2262                        handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
2263                    }
2264                }
2265                break;
2266            }
2267        }
2268
2269        if (handled) {
2270            return true;
2271        }
2272
2273        if (sendToTextFilter(keyCode, count, event)) {
2274            return true;
2275        }
2276
2277        switch (action) {
2278            case KeyEvent.ACTION_DOWN:
2279                return super.onKeyDown(keyCode, event);
2280
2281            case KeyEvent.ACTION_UP:
2282                return super.onKeyUp(keyCode, event);
2283
2284            case KeyEvent.ACTION_MULTIPLE:
2285                return super.onKeyMultiple(keyCode, count, event);
2286
2287            default: // shouldn't happen
2288                return false;
2289        }
2290    }
2291
2292    /**
2293     * Scrolls up or down by the number of items currently present on screen.
2294     *
2295     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2296     * @return whether selection was moved
2297     */
2298    boolean pageScroll(int direction) {
2299        int nextPage = -1;
2300        boolean down = false;
2301
2302        if (direction == FOCUS_UP) {
2303            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2304        } else if (direction == FOCUS_DOWN) {
2305            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2306            down = true;
2307        }
2308
2309        if (nextPage >= 0) {
2310            int position = lookForSelectablePosition(nextPage, down);
2311            if (position >= 0) {
2312                mLayoutMode = LAYOUT_SPECIFIC;
2313                mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2314
2315                if (down && position > mItemCount - getChildCount()) {
2316                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2317                }
2318
2319                if (!down && position < getChildCount()) {
2320                    mLayoutMode = LAYOUT_FORCE_TOP;
2321                }
2322
2323                setSelectionInt(position);
2324                invokeOnItemScrollListener();
2325                if (!awakenScrollBars()) {
2326                    invalidate();
2327                }
2328
2329                return true;
2330            }
2331        }
2332
2333        return false;
2334    }
2335
2336    /**
2337     * Go to the last or first item if possible (not worrying about panning across or navigating
2338     * within the internal focus of the currently selected item.)
2339     *
2340     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2341     *
2342     * @return whether selection was moved
2343     */
2344    boolean fullScroll(int direction) {
2345        boolean moved = false;
2346        if (direction == FOCUS_UP) {
2347            if (mSelectedPosition != 0) {
2348                int position = lookForSelectablePosition(0, true);
2349                if (position >= 0) {
2350                    mLayoutMode = LAYOUT_FORCE_TOP;
2351                    setSelectionInt(position);
2352                    invokeOnItemScrollListener();
2353                }
2354                moved = true;
2355            }
2356        } else if (direction == FOCUS_DOWN) {
2357            if (mSelectedPosition < mItemCount - 1) {
2358                int position = lookForSelectablePosition(mItemCount - 1, true);
2359                if (position >= 0) {
2360                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2361                    setSelectionInt(position);
2362                    invokeOnItemScrollListener();
2363                }
2364                moved = true;
2365            }
2366        }
2367
2368        if (moved && !awakenScrollBars()) {
2369            awakenScrollBars();
2370            invalidate();
2371        }
2372
2373        return moved;
2374    }
2375
2376    /**
2377     * To avoid horizontal focus searches changing the selected item, we
2378     * manually focus search within the selected item (as applicable), and
2379     * prevent focus from jumping to something within another item.
2380     * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2381     * @return Whether this consumes the key event.
2382     */
2383    private boolean handleHorizontalFocusWithinListItem(int direction) {
2384        if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
2385            throw new IllegalArgumentException("direction must be one of"
2386                    + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
2387        }
2388
2389        final int numChildren = getChildCount();
2390        if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2391            final View selectedView = getSelectedView();
2392            if (selectedView != null && selectedView.hasFocus() &&
2393                    selectedView instanceof ViewGroup) {
2394
2395                final View currentFocus = selectedView.findFocus();
2396                final View nextFocus = FocusFinder.getInstance().findNextFocus(
2397                        (ViewGroup) selectedView, currentFocus, direction);
2398                if (nextFocus != null) {
2399                    // do the math to get interesting rect in next focus' coordinates
2400                    currentFocus.getFocusedRect(mTempRect);
2401                    offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2402                    offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2403                    if (nextFocus.requestFocus(direction, mTempRect)) {
2404                        return true;
2405                    }
2406                }
2407                // we are blocking the key from being handled (by returning true)
2408                // if the global result is going to be some other view within this
2409                // list.  this is to acheive the overall goal of having
2410                // horizontal d-pad navigation remain in the current item.
2411                final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2412                        (ViewGroup) getRootView(), currentFocus, direction);
2413                if (globalNextFocus != null) {
2414                    return isViewAncestorOf(globalNextFocus, this);
2415                }
2416            }
2417        }
2418        return false;
2419    }
2420
2421    /**
2422     * Scrolls to the next or previous item if possible.
2423     *
2424     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2425     *
2426     * @return whether selection was moved
2427     */
2428    boolean arrowScroll(int direction) {
2429        try {
2430            mInLayout = true;
2431            final boolean handled = arrowScrollImpl(direction);
2432            if (handled) {
2433                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2434            }
2435            return handled;
2436        } finally {
2437            mInLayout = false;
2438        }
2439    }
2440
2441    /**
2442     * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
2443     * to move to. This return a position in the direction given if the selected item
2444     * is fully visible.
2445     *
2446     * @param selectedView Current selected view to move from
2447     * @param selectedPos Current selected position to move from
2448     * @param direction Direction to move in
2449     * @return Desired selected position after moving in the given direction
2450     */
2451    private final int nextSelectedPositionForDirection(
2452            View selectedView, int selectedPos, int direction) {
2453        int nextSelected;
2454
2455        if (direction == View.FOCUS_DOWN) {
2456            final int listBottom = getHeight() - mListPadding.bottom;
2457            if (selectedView != null && selectedView.getBottom() <= listBottom) {
2458                nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2459                        selectedPos + 1 :
2460                        mFirstPosition;
2461            } else {
2462                return INVALID_POSITION;
2463            }
2464        } else {
2465            final int listTop = mListPadding.top;
2466            if (selectedView != null && selectedView.getTop() >= listTop) {
2467                final int lastPos = mFirstPosition + getChildCount() - 1;
2468                nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2469                        selectedPos - 1 :
2470                        lastPos;
2471            } else {
2472                return INVALID_POSITION;
2473            }
2474        }
2475
2476        if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2477            return INVALID_POSITION;
2478        }
2479        return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2480    }
2481
2482    /**
2483     * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
2484     * whether there are focusable items etc.
2485     *
2486     * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2487     * @return Whether any scrolling, selection or focus change occured.
2488     */
2489    private boolean arrowScrollImpl(int direction) {
2490        if (getChildCount() <= 0) {
2491            return false;
2492        }
2493
2494        View selectedView = getSelectedView();
2495        int selectedPos = mSelectedPosition;
2496
2497        int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
2498        int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2499
2500        // if we are moving focus, we may OVERRIDE the default behavior
2501        final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2502        if (focusResult != null) {
2503            nextSelectedPosition = focusResult.getSelectedPosition();
2504            amountToScroll = focusResult.getAmountToScroll();
2505        }
2506
2507        boolean needToRedraw = focusResult != null;
2508        if (nextSelectedPosition != INVALID_POSITION) {
2509            handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2510            setSelectedPositionInt(nextSelectedPosition);
2511            setNextSelectedPositionInt(nextSelectedPosition);
2512            selectedView = getSelectedView();
2513            selectedPos = nextSelectedPosition;
2514            if (mItemsCanFocus && focusResult == null) {
2515                // there was no new view found to take focus, make sure we
2516                // don't leave focus with the old selection
2517                final View focused = getFocusedChild();
2518                if (focused != null) {
2519                    focused.clearFocus();
2520                }
2521            }
2522            needToRedraw = true;
2523            checkSelectionChanged();
2524        }
2525
2526        if (amountToScroll > 0) {
2527            scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2528            needToRedraw = true;
2529        }
2530
2531        // if we didn't find a new focusable, make sure any existing focused
2532        // item that was panned off screen gives up focus.
2533        if (mItemsCanFocus && (focusResult == null)
2534                && selectedView != null && selectedView.hasFocus()) {
2535            final View focused = selectedView.findFocus();
2536            if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2537                focused.clearFocus();
2538            }
2539        }
2540
2541        // if  the current selection is panned off, we need to remove the selection
2542        if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2543                && !isViewAncestorOf(selectedView, this)) {
2544            selectedView = null;
2545            hideSelector();
2546
2547            // but we don't want to set the ressurect position (that would make subsequent
2548            // unhandled key events bring back the item we just scrolled off!)
2549            mResurrectToPosition = INVALID_POSITION;
2550        }
2551
2552        if (needToRedraw) {
2553            if (selectedView != null) {
2554                positionSelector(selectedPos, selectedView);
2555                mSelectedTop = selectedView.getTop();
2556            }
2557            if (!awakenScrollBars()) {
2558                invalidate();
2559            }
2560            invokeOnItemScrollListener();
2561            return true;
2562        }
2563
2564        return false;
2565    }
2566
2567    /**
2568     * When selection changes, it is possible that the previously selected or the
2569     * next selected item will change its size.  If so, we need to offset some folks,
2570     * and re-layout the items as appropriate.
2571     *
2572     * @param selectedView The currently selected view (before changing selection).
2573     *   should be <code>null</code> if there was no previous selection.
2574     * @param direction Either {@link android.view.View#FOCUS_UP} or
2575     *        {@link android.view.View#FOCUS_DOWN}.
2576     * @param newSelectedPosition The position of the next selection.
2577     * @param newFocusAssigned whether new focus was assigned.  This matters because
2578     *        when something has focus, we don't want to show selection (ugh).
2579     */
2580    private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2581            boolean newFocusAssigned) {
2582        if (newSelectedPosition == INVALID_POSITION) {
2583            throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2584        }
2585
2586        // whether or not we are moving down or up, we want to preserve the
2587        // top of whatever view is on top:
2588        // - moving down: the view that had selection
2589        // - moving up: the view that is getting selection
2590        View topView;
2591        View bottomView;
2592        int topViewIndex, bottomViewIndex;
2593        boolean topSelected = false;
2594        final int selectedIndex = mSelectedPosition - mFirstPosition;
2595        final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2596        if (direction == View.FOCUS_UP) {
2597            topViewIndex = nextSelectedIndex;
2598            bottomViewIndex = selectedIndex;
2599            topView = getChildAt(topViewIndex);
2600            bottomView = selectedView;
2601            topSelected = true;
2602        } else {
2603            topViewIndex = selectedIndex;
2604            bottomViewIndex = nextSelectedIndex;
2605            topView = selectedView;
2606            bottomView = getChildAt(bottomViewIndex);
2607        }
2608
2609        final int numChildren = getChildCount();
2610
2611        // start with top view: is it changing size?
2612        if (topView != null) {
2613            topView.setSelected(!newFocusAssigned && topSelected);
2614            measureAndAdjustDown(topView, topViewIndex, numChildren);
2615        }
2616
2617        // is the bottom view changing size?
2618        if (bottomView != null) {
2619            bottomView.setSelected(!newFocusAssigned && !topSelected);
2620            measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2621        }
2622    }
2623
2624    /**
2625     * Re-measure a child, and if its height changes, lay it out preserving its
2626     * top, and adjust the children below it appropriately.
2627     * @param child The child
2628     * @param childIndex The view group index of the child.
2629     * @param numChildren The number of children in the view group.
2630     */
2631    private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2632        int oldHeight = child.getHeight();
2633        measureItem(child);
2634        if (child.getMeasuredHeight() != oldHeight) {
2635            // lay out the view, preserving its top
2636            relayoutMeasuredItem(child);
2637
2638            // adjust views below appropriately
2639            final int heightDelta = child.getMeasuredHeight() - oldHeight;
2640            for (int i = childIndex + 1; i < numChildren; i++) {
2641                getChildAt(i).offsetTopAndBottom(heightDelta);
2642            }
2643        }
2644    }
2645
2646    /**
2647     * Measure a particular list child.
2648     * TODO: unify with setUpChild.
2649     * @param child The child.
2650     */
2651    private void measureItem(View child) {
2652        ViewGroup.LayoutParams p = child.getLayoutParams();
2653        if (p == null) {
2654            p = new ViewGroup.LayoutParams(
2655                    ViewGroup.LayoutParams.MATCH_PARENT,
2656                    ViewGroup.LayoutParams.WRAP_CONTENT);
2657        }
2658
2659        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2660                mListPadding.left + mListPadding.right, p.width);
2661        int lpHeight = p.height;
2662        int childHeightSpec;
2663        if (lpHeight > 0) {
2664            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2665        } else {
2666            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2667        }
2668        child.measure(childWidthSpec, childHeightSpec);
2669    }
2670
2671    /**
2672     * Layout a child that has been measured, preserving its top position.
2673     * TODO: unify with setUpChild.
2674     * @param child The child.
2675     */
2676    private void relayoutMeasuredItem(View child) {
2677        final int w = child.getMeasuredWidth();
2678        final int h = child.getMeasuredHeight();
2679        final int childLeft = mListPadding.left;
2680        final int childRight = childLeft + w;
2681        final int childTop = child.getTop();
2682        final int childBottom = childTop + h;
2683        child.layout(childLeft, childTop, childRight, childBottom);
2684    }
2685
2686    /**
2687     * @return The amount to preview next items when arrow srolling.
2688     */
2689    private int getArrowScrollPreviewLength() {
2690        return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2691    }
2692
2693    /**
2694     * Determine how much we need to scroll in order to get the next selected view
2695     * visible, with a fading edge showing below as applicable.  The amount is
2696     * capped at {@link #getMaxScrollAmount()} .
2697     *
2698     * @param direction either {@link android.view.View#FOCUS_UP} or
2699     *        {@link android.view.View#FOCUS_DOWN}.
2700     * @param nextSelectedPosition The position of the next selection, or
2701     *        {@link #INVALID_POSITION} if there is no next selectable position
2702     * @return The amount to scroll. Note: this is always positive!  Direction
2703     *         needs to be taken into account when actually scrolling.
2704     */
2705    private int amountToScroll(int direction, int nextSelectedPosition) {
2706        final int listBottom = getHeight() - mListPadding.bottom;
2707        final int listTop = mListPadding.top;
2708
2709        int numChildren = getChildCount();
2710
2711        if (direction == View.FOCUS_DOWN) {
2712            int indexToMakeVisible = numChildren - 1;
2713            if (nextSelectedPosition != INVALID_POSITION) {
2714                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2715            }
2716            while (numChildren <= indexToMakeVisible) {
2717                // Child to view is not attached yet.
2718                addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2719                numChildren++;
2720            }
2721            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2722            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2723
2724            int goalBottom = listBottom;
2725            if (positionToMakeVisible < mItemCount - 1) {
2726                goalBottom -= getArrowScrollPreviewLength();
2727            }
2728
2729            if (viewToMakeVisible.getBottom() <= goalBottom) {
2730                // item is fully visible.
2731                return 0;
2732            }
2733
2734            if (nextSelectedPosition != INVALID_POSITION
2735                    && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2736                // item already has enough of it visible, changing selection is good enough
2737                return 0;
2738            }
2739
2740            int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2741
2742            if ((mFirstPosition + numChildren) == mItemCount) {
2743                // last is last in list -> make sure we don't scroll past it
2744                final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2745                amountToScroll = Math.min(amountToScroll, max);
2746            }
2747
2748            return Math.min(amountToScroll, getMaxScrollAmount());
2749        } else {
2750            int indexToMakeVisible = 0;
2751            if (nextSelectedPosition != INVALID_POSITION) {
2752                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2753            }
2754            while (indexToMakeVisible < 0) {
2755                // Child to view is not attached yet.
2756                addViewAbove(getChildAt(0), mFirstPosition);
2757                mFirstPosition--;
2758                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2759            }
2760            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2761            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2762            int goalTop = listTop;
2763            if (positionToMakeVisible > 0) {
2764                goalTop += getArrowScrollPreviewLength();
2765            }
2766            if (viewToMakeVisible.getTop() >= goalTop) {
2767                // item is fully visible.
2768                return 0;
2769            }
2770
2771            if (nextSelectedPosition != INVALID_POSITION &&
2772                    (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2773                // item already has enough of it visible, changing selection is good enough
2774                return 0;
2775            }
2776
2777            int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2778            if (mFirstPosition == 0) {
2779                // first is first in list -> make sure we don't scroll past it
2780                final int max = listTop - getChildAt(0).getTop();
2781                amountToScroll = Math.min(amountToScroll,  max);
2782            }
2783            return Math.min(amountToScroll, getMaxScrollAmount());
2784        }
2785    }
2786
2787    /**
2788     * Holds results of focus aware arrow scrolling.
2789     */
2790    static private class ArrowScrollFocusResult {
2791        private int mSelectedPosition;
2792        private int mAmountToScroll;
2793
2794        /**
2795         * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2796         */
2797        void populate(int selectedPosition, int amountToScroll) {
2798            mSelectedPosition = selectedPosition;
2799            mAmountToScroll = amountToScroll;
2800        }
2801
2802        public int getSelectedPosition() {
2803            return mSelectedPosition;
2804        }
2805
2806        public int getAmountToScroll() {
2807            return mAmountToScroll;
2808        }
2809    }
2810
2811    /**
2812     * @param direction either {@link android.view.View#FOCUS_UP} or
2813     *        {@link android.view.View#FOCUS_DOWN}.
2814     * @return The position of the next selectable position of the views that
2815     *         are currently visible, taking into account the fact that there might
2816     *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
2817     *         selectable view on screen in the given direction.
2818     */
2819    private int lookForSelectablePositionOnScreen(int direction) {
2820        final int firstPosition = mFirstPosition;
2821        if (direction == View.FOCUS_DOWN) {
2822            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2823                    mSelectedPosition + 1 :
2824                    firstPosition;
2825            if (startPos >= mAdapter.getCount()) {
2826                return INVALID_POSITION;
2827            }
2828            if (startPos < firstPosition) {
2829                startPos = firstPosition;
2830            }
2831
2832            final int lastVisiblePos = getLastVisiblePosition();
2833            final ListAdapter adapter = getAdapter();
2834            for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2835                if (adapter.isEnabled(pos)
2836                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2837                    return pos;
2838                }
2839            }
2840        } else {
2841            int last = firstPosition + getChildCount() - 1;
2842            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2843                    mSelectedPosition - 1 :
2844                    firstPosition + getChildCount() - 1;
2845            if (startPos < 0 || startPos >= mAdapter.getCount()) {
2846                return INVALID_POSITION;
2847            }
2848            if (startPos > last) {
2849                startPos = last;
2850            }
2851
2852            final ListAdapter adapter = getAdapter();
2853            for (int pos = startPos; pos >= firstPosition; pos--) {
2854                if (adapter.isEnabled(pos)
2855                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2856                    return pos;
2857                }
2858            }
2859        }
2860        return INVALID_POSITION;
2861    }
2862
2863    /**
2864     * Do an arrow scroll based on focus searching.  If a new view is
2865     * given focus, return the selection delta and amount to scroll via
2866     * an {@link ArrowScrollFocusResult}, otherwise, return null.
2867     *
2868     * @param direction either {@link android.view.View#FOCUS_UP} or
2869     *        {@link android.view.View#FOCUS_DOWN}.
2870     * @return The result if focus has changed, or <code>null</code>.
2871     */
2872    private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2873        final View selectedView = getSelectedView();
2874        View newFocus;
2875        if (selectedView != null && selectedView.hasFocus()) {
2876            View oldFocus = selectedView.findFocus();
2877            newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2878        } else {
2879            if (direction == View.FOCUS_DOWN) {
2880                final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2881                final int listTop = mListPadding.top +
2882                        (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2883                final int ySearchPoint =
2884                        (selectedView != null && selectedView.getTop() > listTop) ?
2885                                selectedView.getTop() :
2886                                listTop;
2887                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2888            } else {
2889                final boolean bottomFadingEdgeShowing =
2890                        (mFirstPosition + getChildCount() - 1) < mItemCount;
2891                final int listBottom = getHeight() - mListPadding.bottom -
2892                        (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2893                final int ySearchPoint =
2894                        (selectedView != null && selectedView.getBottom() < listBottom) ?
2895                                selectedView.getBottom() :
2896                                listBottom;
2897                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2898            }
2899            newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2900        }
2901
2902        if (newFocus != null) {
2903            final int positionOfNewFocus = positionOfNewFocus(newFocus);
2904
2905            // if the focus change is in a different new position, make sure
2906            // we aren't jumping over another selectable position
2907            if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2908                final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2909                if (selectablePosition != INVALID_POSITION &&
2910                        ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2911                        (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2912                    return null;
2913                }
2914            }
2915
2916            int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2917
2918            final int maxScrollAmount = getMaxScrollAmount();
2919            if (focusScroll < maxScrollAmount) {
2920                // not moving too far, safe to give next view focus
2921                newFocus.requestFocus(direction);
2922                mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2923                return mArrowScrollFocusResult;
2924            } else if (distanceToView(newFocus) < maxScrollAmount){
2925                // Case to consider:
2926                // too far to get entire next focusable on screen, but by going
2927                // max scroll amount, we are getting it at least partially in view,
2928                // so give it focus and scroll the max ammount.
2929                newFocus.requestFocus(direction);
2930                mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2931                return mArrowScrollFocusResult;
2932            }
2933        }
2934        return null;
2935    }
2936
2937    /**
2938     * @param newFocus The view that would have focus.
2939     * @return the position that contains newFocus
2940     */
2941    private int positionOfNewFocus(View newFocus) {
2942        final int numChildren = getChildCount();
2943        for (int i = 0; i < numChildren; i++) {
2944            final View child = getChildAt(i);
2945            if (isViewAncestorOf(newFocus, child)) {
2946                return mFirstPosition + i;
2947            }
2948        }
2949        throw new IllegalArgumentException("newFocus is not a child of any of the"
2950                + " children of the list!");
2951    }
2952
2953    /**
2954     * Return true if child is an ancestor of parent, (or equal to the parent).
2955     */
2956    private boolean isViewAncestorOf(View child, View parent) {
2957        if (child == parent) {
2958            return true;
2959        }
2960
2961        final ViewParent theParent = child.getParent();
2962        return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2963    }
2964
2965    /**
2966     * Determine how much we need to scroll in order to get newFocus in view.
2967     * @param direction either {@link android.view.View#FOCUS_UP} or
2968     *        {@link android.view.View#FOCUS_DOWN}.
2969     * @param newFocus The view that would take focus.
2970     * @param positionOfNewFocus The position of the list item containing newFocus
2971     * @return The amount to scroll.  Note: this is always positive!  Direction
2972     *   needs to be taken into account when actually scrolling.
2973     */
2974    private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2975        int amountToScroll = 0;
2976        newFocus.getDrawingRect(mTempRect);
2977        offsetDescendantRectToMyCoords(newFocus, mTempRect);
2978        if (direction == View.FOCUS_UP) {
2979            if (mTempRect.top < mListPadding.top) {
2980                amountToScroll = mListPadding.top - mTempRect.top;
2981                if (positionOfNewFocus > 0) {
2982                    amountToScroll += getArrowScrollPreviewLength();
2983                }
2984            }
2985        } else {
2986            final int listBottom = getHeight() - mListPadding.bottom;
2987            if (mTempRect.bottom > listBottom) {
2988                amountToScroll = mTempRect.bottom - listBottom;
2989                if (positionOfNewFocus < mItemCount - 1) {
2990                    amountToScroll += getArrowScrollPreviewLength();
2991                }
2992            }
2993        }
2994        return amountToScroll;
2995    }
2996
2997    /**
2998     * Determine the distance to the nearest edge of a view in a particular
2999     * direction.
3000     *
3001     * @param descendant A descendant of this list.
3002     * @return The distance, or 0 if the nearest edge is already on screen.
3003     */
3004    private int distanceToView(View descendant) {
3005        int distance = 0;
3006        descendant.getDrawingRect(mTempRect);
3007        offsetDescendantRectToMyCoords(descendant, mTempRect);
3008        final int listBottom = mBottom - mTop - mListPadding.bottom;
3009        if (mTempRect.bottom < mListPadding.top) {
3010            distance = mListPadding.top - mTempRect.bottom;
3011        } else if (mTempRect.top > listBottom) {
3012            distance = mTempRect.top - listBottom;
3013        }
3014        return distance;
3015    }
3016
3017
3018    /**
3019     * Scroll the children by amount, adding a view at the end and removing
3020     * views that fall off as necessary.
3021     *
3022     * @param amount The amount (positive or negative) to scroll.
3023     */
3024    private void scrollListItemsBy(int amount) {
3025        offsetChildrenTopAndBottom(amount);
3026
3027        final int listBottom = getHeight() - mListPadding.bottom;
3028        final int listTop = mListPadding.top;
3029        final AbsListView.RecycleBin recycleBin = mRecycler;
3030
3031        if (amount < 0) {
3032            // shifted items up
3033
3034            // may need to pan views into the bottom space
3035            int numChildren = getChildCount();
3036            View last = getChildAt(numChildren - 1);
3037            while (last.getBottom() < listBottom) {
3038                final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3039                if (lastVisiblePosition < mItemCount - 1) {
3040                    last = addViewBelow(last, lastVisiblePosition);
3041                    numChildren++;
3042                } else {
3043                    break;
3044                }
3045            }
3046
3047            // may have brought in the last child of the list that is skinnier
3048            // than the fading edge, thereby leaving space at the end.  need
3049            // to shift back
3050            if (last.getBottom() < listBottom) {
3051                offsetChildrenTopAndBottom(listBottom - last.getBottom());
3052            }
3053
3054            // top views may be panned off screen
3055            View first = getChildAt(0);
3056            while (first.getBottom() < listTop) {
3057                AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3058                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3059                    recycleBin.addScrapView(first, mFirstPosition);
3060                }
3061                detachViewFromParent(first);
3062                first = getChildAt(0);
3063                mFirstPosition++;
3064            }
3065        } else {
3066            // shifted items down
3067            View first = getChildAt(0);
3068
3069            // may need to pan views into top
3070            while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3071                first = addViewAbove(first, mFirstPosition);
3072                mFirstPosition--;
3073            }
3074
3075            // may have brought the very first child of the list in too far and
3076            // need to shift it back
3077            if (first.getTop() > listTop) {
3078                offsetChildrenTopAndBottom(listTop - first.getTop());
3079            }
3080
3081            int lastIndex = getChildCount() - 1;
3082            View last = getChildAt(lastIndex);
3083
3084            // bottom view may be panned off screen
3085            while (last.getTop() > listBottom) {
3086                AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3087                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3088                    recycleBin.addScrapView(last, mFirstPosition+lastIndex);
3089                }
3090                detachViewFromParent(last);
3091                last = getChildAt(--lastIndex);
3092            }
3093        }
3094    }
3095
3096    private View addViewAbove(View theView, int position) {
3097        int abovePosition = position - 1;
3098        View view = obtainView(abovePosition, mIsScrap);
3099        int edgeOfNewChild = theView.getTop() - mDividerHeight;
3100        setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3101                false, mIsScrap[0]);
3102        return view;
3103    }
3104
3105    private View addViewBelow(View theView, int position) {
3106        int belowPosition = position + 1;
3107        View view = obtainView(belowPosition, mIsScrap);
3108        int edgeOfNewChild = theView.getBottom() + mDividerHeight;
3109        setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3110                false, mIsScrap[0]);
3111        return view;
3112    }
3113
3114    /**
3115     * Indicates that the views created by the ListAdapter can contain focusable
3116     * items.
3117     *
3118     * @param itemsCanFocus true if items can get focus, false otherwise
3119     */
3120    public void setItemsCanFocus(boolean itemsCanFocus) {
3121        mItemsCanFocus = itemsCanFocus;
3122        if (!itemsCanFocus) {
3123            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3124        }
3125    }
3126
3127    /**
3128     * @return Whether the views created by the ListAdapter can contain focusable
3129     * items.
3130     */
3131    public boolean getItemsCanFocus() {
3132        return mItemsCanFocus;
3133    }
3134
3135    @Override
3136    public boolean isOpaque() {
3137        boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
3138                hasOpaqueScrollbars()) || super.isOpaque();
3139        if (retValue) {
3140            // only return true if the list items cover the entire area of the view
3141            final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
3142            View first = getChildAt(0);
3143            if (first == null || first.getTop() > listTop) {
3144                return false;
3145            }
3146            final int listBottom = getHeight() -
3147                    (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
3148            View last = getChildAt(getChildCount() - 1);
3149            if (last == null || last.getBottom() < listBottom) {
3150                return false;
3151            }
3152        }
3153        return retValue;
3154    }
3155
3156    @Override
3157    public void setCacheColorHint(int color) {
3158        final boolean opaque = (color >>> 24) == 0xFF;
3159        mIsCacheColorOpaque = opaque;
3160        if (opaque) {
3161            if (mDividerPaint == null) {
3162                mDividerPaint = new Paint();
3163            }
3164            mDividerPaint.setColor(color);
3165        }
3166        super.setCacheColorHint(color);
3167    }
3168
3169    void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3170        final int height = drawable.getMinimumHeight();
3171
3172        canvas.save();
3173        canvas.clipRect(bounds);
3174
3175        final int span = bounds.bottom - bounds.top;
3176        if (span < height) {
3177            bounds.top = bounds.bottom - height;
3178        }
3179
3180        drawable.setBounds(bounds);
3181        drawable.draw(canvas);
3182
3183        canvas.restore();
3184    }
3185
3186    void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3187        final int height = drawable.getMinimumHeight();
3188
3189        canvas.save();
3190        canvas.clipRect(bounds);
3191
3192        final int span = bounds.bottom - bounds.top;
3193        if (span < height) {
3194            bounds.bottom = bounds.top + height;
3195        }
3196
3197        drawable.setBounds(bounds);
3198        drawable.draw(canvas);
3199
3200        canvas.restore();
3201    }
3202
3203    @Override
3204    protected void dispatchDraw(Canvas canvas) {
3205        if (mCachingStarted) {
3206            mCachingActive = true;
3207        }
3208
3209        // Draw the dividers
3210        final int dividerHeight = mDividerHeight;
3211        final Drawable overscrollHeader = mOverScrollHeader;
3212        final Drawable overscrollFooter = mOverScrollFooter;
3213        final boolean drawOverscrollHeader = overscrollHeader != null;
3214        final boolean drawOverscrollFooter = overscrollFooter != null;
3215        final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3216
3217        if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
3218            // Only modify the top and bottom in the loop, we set the left and right here
3219            final Rect bounds = mTempRect;
3220            bounds.left = mPaddingLeft;
3221            bounds.right = mRight - mLeft - mPaddingRight;
3222
3223            final int count = getChildCount();
3224            final int headerCount = mHeaderViewInfos.size();
3225            final int itemCount = mItemCount;
3226            final int footerLimit = (itemCount - mFooterViewInfos.size());
3227            final boolean headerDividers = mHeaderDividersEnabled;
3228            final boolean footerDividers = mFooterDividersEnabled;
3229            final int first = mFirstPosition;
3230            final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3231            final ListAdapter adapter = mAdapter;
3232            // If the list is opaque *and* the background is not, we want to
3233            // fill a rect where the dividers would be for non-selectable items
3234            // If the list is opaque and the background is also opaque, we don't
3235            // need to draw anything since the background will do it for us
3236            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3237
3238            if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
3239                mDividerPaint = new Paint();
3240                mDividerPaint.setColor(getCacheColorHint());
3241            }
3242            final Paint paint = mDividerPaint;
3243
3244            int effectivePaddingTop = 0;
3245            int effectivePaddingBottom = 0;
3246            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3247                effectivePaddingTop = mListPadding.top;
3248                effectivePaddingBottom = mListPadding.bottom;
3249            }
3250
3251            final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
3252            if (!mStackFromBottom) {
3253                int bottom = 0;
3254
3255                // Draw top divider or header for overscroll
3256                final int scrollY = mScrollY;
3257                if (count > 0 && scrollY < 0) {
3258                    if (drawOverscrollHeader) {
3259                        bounds.bottom = 0;
3260                        bounds.top = scrollY;
3261                        drawOverscrollHeader(canvas, overscrollHeader, bounds);
3262                    } else if (drawDividers) {
3263                        bounds.bottom = 0;
3264                        bounds.top = -dividerHeight;
3265                        drawDivider(canvas, bounds, -1);
3266                    }
3267                }
3268
3269                for (int i = 0; i < count; i++) {
3270                    final int itemIndex = (first + i);
3271                    final boolean isHeader = (itemIndex < headerCount);
3272                    final boolean isFooter = (itemIndex >= footerLimit);
3273                    if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3274                        final View child = getChildAt(i);
3275                        bottom = child.getBottom();
3276                        final boolean isLastItem = (i == (count - 1));
3277
3278                        if (drawDividers && (bottom < listBottom)
3279                                && !(drawOverscrollFooter && isLastItem)) {
3280                            final int nextIndex = (itemIndex + 1);
3281                            // Draw dividers between enabled items, headers and/or
3282                            // footers when enabled, and the end of the list.
3283                            if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex)
3284                                    || (headerDividers && isHeader)
3285                                    || (footerDividers && isFooter)) && (isLastItem
3286                                    || adapter.isEnabled(nextIndex)
3287                                    || (headerDividers && (nextIndex < headerCount))
3288                                    || (footerDividers && (nextIndex >= footerLimit))))) {
3289                                bounds.top = bottom;
3290                                bounds.bottom = bottom + dividerHeight;
3291                                drawDivider(canvas, bounds, i);
3292                            } else if (fillForMissingDividers) {
3293                                bounds.top = bottom;
3294                                bounds.bottom = bottom + dividerHeight;
3295                                canvas.drawRect(bounds, paint);
3296                            }
3297                        }
3298                    }
3299                }
3300
3301                final int overFooterBottom = mBottom + mScrollY;
3302                if (drawOverscrollFooter && first + count == itemCount &&
3303                        overFooterBottom > bottom) {
3304                    bounds.top = bottom;
3305                    bounds.bottom = overFooterBottom;
3306                    drawOverscrollFooter(canvas, overscrollFooter, bounds);
3307                }
3308            } else {
3309                int top;
3310
3311                final int scrollY = mScrollY;
3312
3313                if (count > 0 && drawOverscrollHeader) {
3314                    bounds.top = scrollY;
3315                    bounds.bottom = getChildAt(0).getTop();
3316                    drawOverscrollHeader(canvas, overscrollHeader, bounds);
3317                }
3318
3319                final int start = drawOverscrollHeader ? 1 : 0;
3320                for (int i = start; i < count; i++) {
3321                    final int itemIndex = (first + i);
3322                    final boolean isHeader = (itemIndex < headerCount);
3323                    final boolean isFooter = (itemIndex >= footerLimit);
3324                    if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3325                        final View child = getChildAt(i);
3326                        top = child.getTop();
3327                        if (drawDividers && (top > effectivePaddingTop)) {
3328                            final boolean isFirstItem = (i == start);
3329                            final int previousIndex = (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)) && (isFirstItem
3335                                    || adapter.isEnabled(previousIndex)
3336                                    || (headerDividers && (previousIndex < headerCount))
3337                                    || (footerDividers && (previousIndex >= footerLimit))))) {
3338                                bounds.top = top - dividerHeight;
3339                                bounds.bottom = top;
3340                                // Give the method the child ABOVE the divider,
3341                                // so we subtract one from our child position.
3342                                // Give -1 when there is no child above the
3343                                // divider.
3344                                drawDivider(canvas, bounds, i - 1);
3345                            } else if (fillForMissingDividers) {
3346                                bounds.top = top - dividerHeight;
3347                                bounds.bottom = top;
3348                                canvas.drawRect(bounds, paint);
3349                            }
3350                        }
3351                    }
3352                }
3353
3354                if (count > 0 && scrollY > 0) {
3355                    if (drawOverscrollFooter) {
3356                        final int absListBottom = mBottom;
3357                        bounds.top = absListBottom;
3358                        bounds.bottom = absListBottom + scrollY;
3359                        drawOverscrollFooter(canvas, overscrollFooter, bounds);
3360                    } else if (drawDividers) {
3361                        bounds.top = listBottom;
3362                        bounds.bottom = listBottom + dividerHeight;
3363                        drawDivider(canvas, bounds, -1);
3364                    }
3365                }
3366            }
3367        }
3368
3369        // Draw the indicators (these should be drawn above the dividers) and children
3370        super.dispatchDraw(canvas);
3371    }
3372
3373    @Override
3374    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3375        boolean more = super.drawChild(canvas, child, drawingTime);
3376        if (mCachingActive && child.mCachingFailed) {
3377            mCachingActive = false;
3378        }
3379        return more;
3380    }
3381
3382    /**
3383     * Draws a divider for the given child in the given bounds.
3384     *
3385     * @param canvas The canvas to draw to.
3386     * @param bounds The bounds of the divider.
3387     * @param childIndex The index of child (of the View) above the divider.
3388     *            This will be -1 if there is no child above the divider to be
3389     *            drawn.
3390     */
3391    void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3392        // This widget draws the same divider for all children
3393        final Drawable divider = mDivider;
3394
3395        divider.setBounds(bounds);
3396        divider.draw(canvas);
3397    }
3398
3399    /**
3400     * Returns the drawable that will be drawn between each item in the list.
3401     *
3402     * @return the current drawable drawn between list elements
3403     */
3404    public Drawable getDivider() {
3405        return mDivider;
3406    }
3407
3408    /**
3409     * Sets the drawable that will be drawn between each item in the list. If the drawable does
3410     * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3411     *
3412     * @param divider The drawable to use.
3413     */
3414    public void setDivider(Drawable divider) {
3415        if (divider != null) {
3416            mDividerHeight = divider.getIntrinsicHeight();
3417        } else {
3418            mDividerHeight = 0;
3419        }
3420        mDivider = divider;
3421        mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
3422        requestLayout();
3423        invalidate();
3424    }
3425
3426    /**
3427     * @return Returns the height of the divider that will be drawn between each item in the list.
3428     */
3429    public int getDividerHeight() {
3430        return mDividerHeight;
3431    }
3432
3433    /**
3434     * Sets the height of the divider that will be drawn between each item in the list. Calling
3435     * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3436     *
3437     * @param height The new height of the divider in pixels.
3438     */
3439    public void setDividerHeight(int height) {
3440        mDividerHeight = height;
3441        requestLayout();
3442        invalidate();
3443    }
3444
3445    /**
3446     * Enables or disables the drawing of the divider for header views.
3447     *
3448     * @param headerDividersEnabled True to draw the headers, false otherwise.
3449     *
3450     * @see #setFooterDividersEnabled(boolean)
3451     * @see #areHeaderDividersEnabled()
3452     * @see #addHeaderView(android.view.View)
3453     */
3454    public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3455        mHeaderDividersEnabled = headerDividersEnabled;
3456        invalidate();
3457    }
3458
3459    /**
3460     * @return Whether the drawing of the divider for header views is enabled
3461     *
3462     * @see #setHeaderDividersEnabled(boolean)
3463     */
3464    public boolean areHeaderDividersEnabled() {
3465        return mHeaderDividersEnabled;
3466    }
3467
3468    /**
3469     * Enables or disables the drawing of the divider for footer views.
3470     *
3471     * @param footerDividersEnabled True to draw the footers, false otherwise.
3472     *
3473     * @see #setHeaderDividersEnabled(boolean)
3474     * @see #areFooterDividersEnabled()
3475     * @see #addFooterView(android.view.View)
3476     */
3477    public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3478        mFooterDividersEnabled = footerDividersEnabled;
3479        invalidate();
3480    }
3481
3482    /**
3483     * @return Whether the drawing of the divider for footer views is enabled
3484     *
3485     * @see #setFooterDividersEnabled(boolean)
3486     */
3487    public boolean areFooterDividersEnabled() {
3488        return mFooterDividersEnabled;
3489    }
3490
3491    /**
3492     * Sets the drawable that will be drawn above all other list content.
3493     * This area can become visible when the user overscrolls the list.
3494     *
3495     * @param header The drawable to use
3496     */
3497    public void setOverscrollHeader(Drawable header) {
3498        mOverScrollHeader = header;
3499        if (mScrollY < 0) {
3500            invalidate();
3501        }
3502    }
3503
3504    /**
3505     * @return The drawable that will be drawn above all other list content
3506     */
3507    public Drawable getOverscrollHeader() {
3508        return mOverScrollHeader;
3509    }
3510
3511    /**
3512     * Sets the drawable that will be drawn below all other list content.
3513     * This area can become visible when the user overscrolls the list,
3514     * or when the list's content does not fully fill the container area.
3515     *
3516     * @param footer The drawable to use
3517     */
3518    public void setOverscrollFooter(Drawable footer) {
3519        mOverScrollFooter = footer;
3520        invalidate();
3521    }
3522
3523    /**
3524     * @return The drawable that will be drawn below all other list content
3525     */
3526    public Drawable getOverscrollFooter() {
3527        return mOverScrollFooter;
3528    }
3529
3530    @Override
3531    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3532        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3533
3534        final ListAdapter adapter = mAdapter;
3535        int closetChildIndex = -1;
3536        int closestChildTop = 0;
3537        if (adapter != null && gainFocus && previouslyFocusedRect != null) {
3538            previouslyFocusedRect.offset(mScrollX, mScrollY);
3539
3540            // Don't cache the result of getChildCount or mFirstPosition here,
3541            // it could change in layoutChildren.
3542            if (adapter.getCount() < getChildCount() + mFirstPosition) {
3543                mLayoutMode = LAYOUT_NORMAL;
3544                layoutChildren();
3545            }
3546
3547            // figure out which item should be selected based on previously
3548            // focused rect
3549            Rect otherRect = mTempRect;
3550            int minDistance = Integer.MAX_VALUE;
3551            final int childCount = getChildCount();
3552            final int firstPosition = mFirstPosition;
3553
3554            for (int i = 0; i < childCount; i++) {
3555                // only consider selectable views
3556                if (!adapter.isEnabled(firstPosition + i)) {
3557                    continue;
3558                }
3559
3560                View other = getChildAt(i);
3561                other.getDrawingRect(otherRect);
3562                offsetDescendantRectToMyCoords(other, otherRect);
3563                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3564
3565                if (distance < minDistance) {
3566                    minDistance = distance;
3567                    closetChildIndex = i;
3568                    closestChildTop = other.getTop();
3569                }
3570            }
3571        }
3572
3573        if (closetChildIndex >= 0) {
3574            setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
3575        } else {
3576            requestLayout();
3577        }
3578    }
3579
3580
3581    /*
3582     * (non-Javadoc)
3583     *
3584     * Children specified in XML are assumed to be header views. After we have
3585     * parsed them move them out of the children list and into mHeaderViews.
3586     */
3587    @Override
3588    protected void onFinishInflate() {
3589        super.onFinishInflate();
3590
3591        int count = getChildCount();
3592        if (count > 0) {
3593            for (int i = 0; i < count; ++i) {
3594                addHeaderView(getChildAt(i));
3595            }
3596            removeAllViews();
3597        }
3598    }
3599
3600    /* (non-Javadoc)
3601     * @see android.view.View#findViewById(int)
3602     * First look in our children, then in any header and footer views that may be scrolled off.
3603     */
3604    @Override
3605    protected View findViewTraversal(int id) {
3606        View v;
3607        v = super.findViewTraversal(id);
3608        if (v == null) {
3609            v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3610            if (v != null) {
3611                return v;
3612            }
3613            v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3614            if (v != null) {
3615                return v;
3616            }
3617        }
3618        return v;
3619    }
3620
3621    /* (non-Javadoc)
3622     *
3623     * Look in the passed in list of headers or footers for the view.
3624     */
3625    View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3626        if (where != null) {
3627            int len = where.size();
3628            View v;
3629
3630            for (int i = 0; i < len; i++) {
3631                v = where.get(i).view;
3632
3633                if (!v.isRootNamespace()) {
3634                    v = v.findViewById(id);
3635
3636                    if (v != null) {
3637                        return v;
3638                    }
3639                }
3640            }
3641        }
3642        return null;
3643    }
3644
3645    /* (non-Javadoc)
3646     * @see android.view.View#findViewWithTag(Object)
3647     * First look in our children, then in any header and footer views that may be scrolled off.
3648     */
3649    @Override
3650    protected View findViewWithTagTraversal(Object tag) {
3651        View v;
3652        v = super.findViewWithTagTraversal(tag);
3653        if (v == null) {
3654            v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
3655            if (v != null) {
3656                return v;
3657            }
3658
3659            v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
3660            if (v != null) {
3661                return v;
3662            }
3663        }
3664        return v;
3665    }
3666
3667    /* (non-Javadoc)
3668     *
3669     * Look in the passed in list of headers or footers for the view with the tag.
3670     */
3671    View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
3672        if (where != null) {
3673            int len = where.size();
3674            View v;
3675
3676            for (int i = 0; i < len; i++) {
3677                v = where.get(i).view;
3678
3679                if (!v.isRootNamespace()) {
3680                    v = v.findViewWithTag(tag);
3681
3682                    if (v != null) {
3683                        return v;
3684                    }
3685                }
3686            }
3687        }
3688        return null;
3689    }
3690
3691    /**
3692     * @hide
3693     * @see android.view.View#findViewByPredicate(Predicate)
3694     * First look in our children, then in any header and footer views that may be scrolled off.
3695     */
3696    @Override
3697    protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
3698        View v;
3699        v = super.findViewByPredicateTraversal(predicate, childToSkip);
3700        if (v == null) {
3701            v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
3702            if (v != null) {
3703                return v;
3704            }
3705
3706            v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
3707            if (v != null) {
3708                return v;
3709            }
3710        }
3711        return v;
3712    }
3713
3714    /* (non-Javadoc)
3715     *
3716     * Look in the passed in list of headers or footers for the first view that matches
3717     * the predicate.
3718     */
3719    View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
3720            Predicate<View> predicate, View childToSkip) {
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 != childToSkip && !v.isRootNamespace()) {
3729                    v = v.findViewByPredicate(predicate);
3730
3731                    if (v != null) {
3732                        return v;
3733                    }
3734                }
3735            }
3736        }
3737        return null;
3738    }
3739
3740    /**
3741     * Returns the set of checked items ids. The result is only valid if the
3742     * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3743     *
3744     * @return A new array which contains the id of each checked item in the
3745     *         list.
3746     *
3747     * @deprecated Use {@link #getCheckedItemIds()} instead.
3748     */
3749    @Deprecated
3750    public long[] getCheckItemIds() {
3751        // Use new behavior that correctly handles stable ID mapping.
3752        if (mAdapter != null && mAdapter.hasStableIds()) {
3753            return getCheckedItemIds();
3754        }
3755
3756        // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3757        // Fall back to it to support legacy apps.
3758        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3759            final SparseBooleanArray states = mCheckStates;
3760            final int count = states.size();
3761            final long[] ids = new long[count];
3762            final ListAdapter adapter = mAdapter;
3763
3764            int checkedCount = 0;
3765            for (int i = 0; i < count; i++) {
3766                if (states.valueAt(i)) {
3767                    ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3768                }
3769            }
3770
3771            // Trim array if needed. mCheckStates may contain false values
3772            // resulting in checkedCount being smaller than count.
3773            if (checkedCount == count) {
3774                return ids;
3775            } else {
3776                final long[] result = new long[checkedCount];
3777                System.arraycopy(ids, 0, result, 0, checkedCount);
3778
3779                return result;
3780            }
3781        }
3782        return new long[0];
3783    }
3784
3785    @Override
3786    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
3787        super.onInitializeAccessibilityEvent(event);
3788        event.setClassName(ListView.class.getName());
3789    }
3790
3791    @Override
3792    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
3793        super.onInitializeAccessibilityNodeInfo(info);
3794        info.setClassName(ListView.class.getName());
3795    }
3796}
3797