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