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