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