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