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