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