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