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    private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
116    private 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                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
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                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
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 = new HeaderViewListAdapter(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     * Obtain the view and add it to our list of children. The view can be made
1943     * fresh, converted from an unused view, or used as is if it was in the
1944     * 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 If flow is true, align top edge to y. If false, align bottom
1949     *        edge to y.
1950     * @param childrenLeft Left edge where children should be positioned
1951     * @param selected Is this position selected?
1952     * @return View that was added
1953     */
1954    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1955            boolean selected) {
1956        View child;
1957
1958
1959        if (!mDataChanged) {
1960            // Try to use an existing view for this position
1961            child = mRecycler.getActiveView(position);
1962            if (child != null) {
1963                // Found it -- we're using an existing child
1964                // This just needs to be positioned
1965                setupChild(child, position, y, flow, childrenLeft, selected, true);
1966
1967                return child;
1968            }
1969        }
1970
1971        // Make a new view for this position, or convert an unused view if possible
1972        child = obtainView(position, mIsScrap);
1973
1974        // This needs to be positioned and measured
1975        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
1976
1977        return child;
1978    }
1979
1980    /**
1981     * Add a view as a child and make sure it is measured (if necessary) and
1982     * positioned properly.
1983     *
1984     * @param child The view to add
1985     * @param position The position of this child
1986     * @param y The y position relative to which this view will be positioned
1987     * @param flowDown If true, align top edge to y. If false, align bottom
1988     *        edge to y.
1989     * @param childrenLeft Left edge where children should be positioned
1990     * @param selected Is this position selected?
1991     * @param recycled Has this view been pulled from the recycle bin? If so it
1992     *        does not need to be remeasured.
1993     */
1994    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1995            boolean selected, boolean recycled) {
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 = !recycled || updateChildSelected || child.isLayoutRequested();
2005
2006        // Respect layout params that are already in the view. Otherwise make some up...
2007        // noinspection unchecked
2008        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
2009        if (p == null) {
2010            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
2011        }
2012        p.viewType = mAdapter.getItemViewType(position);
2013        p.isEnabled = mAdapter.isEnabled(position);
2014
2015        if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
2016                && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
2017            attachViewToParent(child, flowDown ? -1 : 0, p);
2018        } else {
2019            p.forceAdd = false;
2020            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
2021                p.recycledHeaderFooter = true;
2022            }
2023            addViewInLayout(child, flowDown ? -1 : 0, p, true);
2024        }
2025
2026        if (updateChildSelected) {
2027            child.setSelected(isSelected);
2028        }
2029
2030        if (updateChildPressed) {
2031            child.setPressed(isPressed);
2032        }
2033
2034        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
2035            if (child instanceof Checkable) {
2036                ((Checkable) child).setChecked(mCheckStates.get(position));
2037            } else if (getContext().getApplicationInfo().targetSdkVersion
2038                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
2039                child.setActivated(mCheckStates.get(position));
2040            }
2041        }
2042
2043        if (needToMeasure) {
2044            final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2045                    mListPadding.left + mListPadding.right, p.width);
2046            final int lpHeight = p.height;
2047            final int childHeightSpec;
2048            if (lpHeight > 0) {
2049                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2050            } else {
2051                childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2052                        MeasureSpec.UNSPECIFIED);
2053            }
2054            child.measure(childWidthSpec, childHeightSpec);
2055        } else {
2056            cleanupLayoutState(child);
2057        }
2058
2059        final int w = child.getMeasuredWidth();
2060        final int h = child.getMeasuredHeight();
2061        final int childTop = flowDown ? y : y - h;
2062
2063        if (needToMeasure) {
2064            final int childRight = childrenLeft + w;
2065            final int childBottom = childTop + h;
2066            child.layout(childrenLeft, childTop, childRight, childBottom);
2067        } else {
2068            child.offsetLeftAndRight(childrenLeft - child.getLeft());
2069            child.offsetTopAndBottom(childTop - child.getTop());
2070        }
2071
2072        if (mCachingStarted && !child.isDrawingCacheEnabled()) {
2073            child.setDrawingCacheEnabled(true);
2074        }
2075
2076        if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
2077                != position) {
2078            child.jumpDrawablesToCurrentState();
2079        }
2080
2081        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2082    }
2083
2084    @Override
2085    protected boolean canAnimate() {
2086        return super.canAnimate() && mItemCount > 0;
2087    }
2088
2089    /**
2090     * Sets the currently selected item. If in touch mode, the item will not be selected
2091     * but it will still be positioned appropriately. If the specified selection position
2092     * is less than 0, then the item at position 0 will be selected.
2093     *
2094     * @param position Index (starting at 0) of the data item to be selected.
2095     */
2096    @Override
2097    public void setSelection(int position) {
2098        setSelectionFromTop(position, 0);
2099    }
2100
2101    /**
2102     * Makes the item at the supplied position selected.
2103     *
2104     * @param position the position of the item to select
2105     */
2106    @Override
2107    void setSelectionInt(int position) {
2108        setNextSelectedPositionInt(position);
2109        boolean awakeScrollbars = false;
2110
2111        final int selectedPosition = mSelectedPosition;
2112
2113        if (selectedPosition >= 0) {
2114            if (position == selectedPosition - 1) {
2115                awakeScrollbars = true;
2116            } else if (position == selectedPosition + 1) {
2117                awakeScrollbars = true;
2118            }
2119        }
2120
2121        if (mPositionScroller != null) {
2122            mPositionScroller.stop();
2123        }
2124
2125        layoutChildren();
2126
2127        if (awakeScrollbars) {
2128            awakenScrollBars();
2129        }
2130    }
2131
2132    /**
2133     * Find a position that can be selected (i.e., is not a separator).
2134     *
2135     * @param position The starting position to look at.
2136     * @param lookDown Whether to look down for other positions.
2137     * @return The next selectable position starting at position and then searching either up or
2138     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
2139     */
2140    @Override
2141    int lookForSelectablePosition(int position, boolean lookDown) {
2142        final ListAdapter adapter = mAdapter;
2143        if (adapter == null || isInTouchMode()) {
2144            return INVALID_POSITION;
2145        }
2146
2147        final int count = adapter.getCount();
2148        if (!mAreAllItemsSelectable) {
2149            if (lookDown) {
2150                position = Math.max(0, position);
2151                while (position < count && !adapter.isEnabled(position)) {
2152                    position++;
2153                }
2154            } else {
2155                position = Math.min(position, count - 1);
2156                while (position >= 0 && !adapter.isEnabled(position)) {
2157                    position--;
2158                }
2159            }
2160        }
2161
2162        if (position < 0 || position >= count) {
2163            return INVALID_POSITION;
2164        }
2165
2166        return position;
2167    }
2168
2169    /**
2170     * Find a position that can be selected (i.e., is not a separator). If there
2171     * are no selectable positions in the specified direction from the starting
2172     * position, searches in the opposite direction from the starting position
2173     * to the current position.
2174     *
2175     * @param current the current position
2176     * @param position the starting position
2177     * @param lookDown whether to look down for other positions
2178     * @return the next selectable position, or {@link #INVALID_POSITION} if
2179     *         nothing can be found
2180     */
2181    int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2182        final ListAdapter adapter = mAdapter;
2183        if (adapter == null || isInTouchMode()) {
2184            return INVALID_POSITION;
2185        }
2186
2187        // First check after the starting position in the specified direction.
2188        final int after = lookForSelectablePosition(position, lookDown);
2189        if (after != INVALID_POSITION) {
2190            return after;
2191        }
2192
2193        // Then check between the starting position and the current position.
2194        final int count = adapter.getCount();
2195        current = MathUtils.constrain(current, -1, count - 1);
2196        if (lookDown) {
2197            position = Math.min(position - 1, count - 1);
2198            while ((position > current) && !adapter.isEnabled(position)) {
2199                position--;
2200            }
2201            if (position <= current) {
2202                return INVALID_POSITION;
2203            }
2204        } else {
2205            position = Math.max(0, position + 1);
2206            while ((position < current) && !adapter.isEnabled(position)) {
2207                position++;
2208            }
2209            if (position >= current) {
2210                return INVALID_POSITION;
2211            }
2212        }
2213
2214        return position;
2215    }
2216
2217    /**
2218     * setSelectionAfterHeaderView set the selection to be the first list item
2219     * after the header views.
2220     */
2221    public void setSelectionAfterHeaderView() {
2222        final int count = mHeaderViewInfos.size();
2223        if (count > 0) {
2224            mNextSelectedPosition = 0;
2225            return;
2226        }
2227
2228        if (mAdapter != null) {
2229            setSelection(count);
2230        } else {
2231            mNextSelectedPosition = count;
2232            mLayoutMode = LAYOUT_SET_SELECTION;
2233        }
2234
2235    }
2236
2237    @Override
2238    public boolean dispatchKeyEvent(KeyEvent event) {
2239        // Dispatch in the normal way
2240        boolean handled = super.dispatchKeyEvent(event);
2241        if (!handled) {
2242            // If we didn't handle it...
2243            View focused = getFocusedChild();
2244            if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2245                // ... and our focused child didn't handle it
2246                // ... give it to ourselves so we can scroll if necessary
2247                handled = onKeyDown(event.getKeyCode(), event);
2248            }
2249        }
2250        return handled;
2251    }
2252
2253    @Override
2254    public boolean onKeyDown(int keyCode, KeyEvent event) {
2255        return commonKey(keyCode, 1, event);
2256    }
2257
2258    @Override
2259    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2260        return commonKey(keyCode, repeatCount, event);
2261    }
2262
2263    @Override
2264    public boolean onKeyUp(int keyCode, KeyEvent event) {
2265        return commonKey(keyCode, 1, event);
2266    }
2267
2268    private boolean commonKey(int keyCode, int count, KeyEvent event) {
2269        if (mAdapter == null || !isAttachedToWindow()) {
2270            return false;
2271        }
2272
2273        if (mDataChanged) {
2274            layoutChildren();
2275        }
2276
2277        boolean handled = false;
2278        int action = event.getAction();
2279        if (KeyEvent.isConfirmKey(keyCode)
2280                && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
2281            handled = resurrectSelectionIfNeeded();
2282            if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
2283                keyPressed();
2284                handled = true;
2285            }
2286        }
2287
2288
2289        if (!handled && action != KeyEvent.ACTION_UP) {
2290            switch (keyCode) {
2291            case KeyEvent.KEYCODE_DPAD_UP:
2292                if (event.hasNoModifiers()) {
2293                    handled = resurrectSelectionIfNeeded();
2294                    if (!handled) {
2295                        while (count-- > 0) {
2296                            if (arrowScroll(FOCUS_UP)) {
2297                                handled = true;
2298                            } else {
2299                                break;
2300                            }
2301                        }
2302                    }
2303                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2304                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2305                }
2306                break;
2307
2308            case KeyEvent.KEYCODE_DPAD_DOWN:
2309                if (event.hasNoModifiers()) {
2310                    handled = resurrectSelectionIfNeeded();
2311                    if (!handled) {
2312                        while (count-- > 0) {
2313                            if (arrowScroll(FOCUS_DOWN)) {
2314                                handled = true;
2315                            } else {
2316                                break;
2317                            }
2318                        }
2319                    }
2320                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2321                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2322                }
2323                break;
2324
2325            case KeyEvent.KEYCODE_DPAD_LEFT:
2326                if (event.hasNoModifiers()) {
2327                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2328                }
2329                break;
2330
2331            case KeyEvent.KEYCODE_DPAD_RIGHT:
2332                if (event.hasNoModifiers()) {
2333                    handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2334                }
2335                break;
2336
2337            case KeyEvent.KEYCODE_PAGE_UP:
2338                if (event.hasNoModifiers()) {
2339                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
2340                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2341                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2342                }
2343                break;
2344
2345            case KeyEvent.KEYCODE_PAGE_DOWN:
2346                if (event.hasNoModifiers()) {
2347                    handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
2348                } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
2349                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2350                }
2351                break;
2352
2353            case KeyEvent.KEYCODE_MOVE_HOME:
2354                if (event.hasNoModifiers()) {
2355                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
2356                }
2357                break;
2358
2359            case KeyEvent.KEYCODE_MOVE_END:
2360                if (event.hasNoModifiers()) {
2361                    handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
2362                }
2363                break;
2364
2365            case KeyEvent.KEYCODE_TAB:
2366                // This creates an asymmetry in TAB navigation order. At some
2367                // point in the future we may decide that it's preferable to
2368                // force the list selection to the top or bottom when receiving
2369                // TAB focus from another widget, but for now this is adequate.
2370                if (event.hasNoModifiers()) {
2371                    handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2372                } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2373                    handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
2374                }
2375                break;
2376            }
2377        }
2378
2379        if (handled) {
2380            return true;
2381        }
2382
2383        if (sendToTextFilter(keyCode, count, event)) {
2384            return true;
2385        }
2386
2387        switch (action) {
2388            case KeyEvent.ACTION_DOWN:
2389                return super.onKeyDown(keyCode, event);
2390
2391            case KeyEvent.ACTION_UP:
2392                return super.onKeyUp(keyCode, event);
2393
2394            case KeyEvent.ACTION_MULTIPLE:
2395                return super.onKeyMultiple(keyCode, count, event);
2396
2397            default: // shouldn't happen
2398                return false;
2399        }
2400    }
2401
2402    /**
2403     * Scrolls up or down by the number of items currently present on screen.
2404     *
2405     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2406     * @return whether selection was moved
2407     */
2408    boolean pageScroll(int direction) {
2409        final int nextPage;
2410        final boolean down;
2411
2412        if (direction == FOCUS_UP) {
2413            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2414            down = false;
2415        } else if (direction == FOCUS_DOWN) {
2416            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2417            down = true;
2418        } else {
2419            return false;
2420        }
2421
2422        if (nextPage >= 0) {
2423            final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
2424            if (position >= 0) {
2425                mLayoutMode = LAYOUT_SPECIFIC;
2426                mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2427
2428                if (down && (position > (mItemCount - getChildCount()))) {
2429                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2430                }
2431
2432                if (!down && (position < getChildCount())) {
2433                    mLayoutMode = LAYOUT_FORCE_TOP;
2434                }
2435
2436                setSelectionInt(position);
2437                invokeOnItemScrollListener();
2438                if (!awakenScrollBars()) {
2439                    invalidate();
2440                }
2441
2442                return true;
2443            }
2444        }
2445
2446        return false;
2447    }
2448
2449    /**
2450     * Go to the last or first item if possible (not worrying about panning
2451     * across or navigating within the internal focus of the currently selected
2452     * item.)
2453     *
2454     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2455     * @return whether selection was moved
2456     */
2457    boolean fullScroll(int direction) {
2458        boolean moved = false;
2459        if (direction == FOCUS_UP) {
2460            if (mSelectedPosition != 0) {
2461                final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
2462                if (position >= 0) {
2463                    mLayoutMode = LAYOUT_FORCE_TOP;
2464                    setSelectionInt(position);
2465                    invokeOnItemScrollListener();
2466                }
2467                moved = true;
2468            }
2469        } else if (direction == FOCUS_DOWN) {
2470            final int lastItem = (mItemCount - 1);
2471            if (mSelectedPosition < lastItem) {
2472                final int position = lookForSelectablePositionAfter(
2473                        mSelectedPosition, lastItem, false);
2474                if (position >= 0) {
2475                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2476                    setSelectionInt(position);
2477                    invokeOnItemScrollListener();
2478                }
2479                moved = true;
2480            }
2481        }
2482
2483        if (moved && !awakenScrollBars()) {
2484            awakenScrollBars();
2485            invalidate();
2486        }
2487
2488        return moved;
2489    }
2490
2491    /**
2492     * To avoid horizontal focus searches changing the selected item, we
2493     * manually focus search within the selected item (as applicable), and
2494     * prevent focus from jumping to something within another item.
2495     * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2496     * @return Whether this consumes the key event.
2497     */
2498    private boolean handleHorizontalFocusWithinListItem(int direction) {
2499        if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
2500            throw new IllegalArgumentException("direction must be one of"
2501                    + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
2502        }
2503
2504        final int numChildren = getChildCount();
2505        if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2506            final View selectedView = getSelectedView();
2507            if (selectedView != null && selectedView.hasFocus() &&
2508                    selectedView instanceof ViewGroup) {
2509
2510                final View currentFocus = selectedView.findFocus();
2511                final View nextFocus = FocusFinder.getInstance().findNextFocus(
2512                        (ViewGroup) selectedView, currentFocus, direction);
2513                if (nextFocus != null) {
2514                    // do the math to get interesting rect in next focus' coordinates
2515                    Rect focusedRect = mTempRect;
2516                    if (currentFocus != null) {
2517                        currentFocus.getFocusedRect(focusedRect);
2518                        offsetDescendantRectToMyCoords(currentFocus, focusedRect);
2519                        offsetRectIntoDescendantCoords(nextFocus, focusedRect);
2520                    } else {
2521                        focusedRect = null;
2522                    }
2523                    if (nextFocus.requestFocus(direction, focusedRect)) {
2524                        return true;
2525                    }
2526                }
2527                // we are blocking the key from being handled (by returning true)
2528                // if the global result is going to be some other view within this
2529                // list.  this is to acheive the overall goal of having
2530                // horizontal d-pad navigation remain in the current item.
2531                final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2532                        (ViewGroup) getRootView(), currentFocus, direction);
2533                if (globalNextFocus != null) {
2534                    return isViewAncestorOf(globalNextFocus, this);
2535                }
2536            }
2537        }
2538        return false;
2539    }
2540
2541    /**
2542     * Scrolls to the next or previous item if possible.
2543     *
2544     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2545     *
2546     * @return whether selection was moved
2547     */
2548    boolean arrowScroll(int direction) {
2549        try {
2550            mInLayout = true;
2551            final boolean handled = arrowScrollImpl(direction);
2552            if (handled) {
2553                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2554            }
2555            return handled;
2556        } finally {
2557            mInLayout = false;
2558        }
2559    }
2560
2561    /**
2562     * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
2563     * to move to. This return a position in the direction given if the selected item
2564     * is fully visible.
2565     *
2566     * @param selectedView Current selected view to move from
2567     * @param selectedPos Current selected position to move from
2568     * @param direction Direction to move in
2569     * @return Desired selected position after moving in the given direction
2570     */
2571    private final int nextSelectedPositionForDirection(
2572            View selectedView, int selectedPos, int direction) {
2573        int nextSelected;
2574
2575        if (direction == View.FOCUS_DOWN) {
2576            final int listBottom = getHeight() - mListPadding.bottom;
2577            if (selectedView != null && selectedView.getBottom() <= listBottom) {
2578                nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2579                        selectedPos + 1 :
2580                        mFirstPosition;
2581            } else {
2582                return INVALID_POSITION;
2583            }
2584        } else {
2585            final int listTop = mListPadding.top;
2586            if (selectedView != null && selectedView.getTop() >= listTop) {
2587                final int lastPos = mFirstPosition + getChildCount() - 1;
2588                nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2589                        selectedPos - 1 :
2590                        lastPos;
2591            } else {
2592                return INVALID_POSITION;
2593            }
2594        }
2595
2596        if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2597            return INVALID_POSITION;
2598        }
2599        return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2600    }
2601
2602    /**
2603     * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
2604     * whether there are focusable items etc.
2605     *
2606     * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2607     * @return Whether any scrolling, selection or focus change occured.
2608     */
2609    private boolean arrowScrollImpl(int direction) {
2610        if (getChildCount() <= 0) {
2611            return false;
2612        }
2613
2614        View selectedView = getSelectedView();
2615        int selectedPos = mSelectedPosition;
2616
2617        int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
2618        int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2619
2620        // if we are moving focus, we may OVERRIDE the default behavior
2621        final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2622        if (focusResult != null) {
2623            nextSelectedPosition = focusResult.getSelectedPosition();
2624            amountToScroll = focusResult.getAmountToScroll();
2625        }
2626
2627        boolean needToRedraw = focusResult != null;
2628        if (nextSelectedPosition != INVALID_POSITION) {
2629            handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2630            setSelectedPositionInt(nextSelectedPosition);
2631            setNextSelectedPositionInt(nextSelectedPosition);
2632            selectedView = getSelectedView();
2633            selectedPos = nextSelectedPosition;
2634            if (mItemsCanFocus && focusResult == null) {
2635                // there was no new view found to take focus, make sure we
2636                // don't leave focus with the old selection
2637                final View focused = getFocusedChild();
2638                if (focused != null) {
2639                    focused.clearFocus();
2640                }
2641            }
2642            needToRedraw = true;
2643            checkSelectionChanged();
2644        }
2645
2646        if (amountToScroll > 0) {
2647            scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2648            needToRedraw = true;
2649        }
2650
2651        // if we didn't find a new focusable, make sure any existing focused
2652        // item that was panned off screen gives up focus.
2653        if (mItemsCanFocus && (focusResult == null)
2654                && selectedView != null && selectedView.hasFocus()) {
2655            final View focused = selectedView.findFocus();
2656            if (focused != null) {
2657                if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2658                    focused.clearFocus();
2659                }
2660            }
2661        }
2662
2663        // if  the current selection is panned off, we need to remove the selection
2664        if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2665                && !isViewAncestorOf(selectedView, this)) {
2666            selectedView = null;
2667            hideSelector();
2668
2669            // but we don't want to set the ressurect position (that would make subsequent
2670            // unhandled key events bring back the item we just scrolled off!)
2671            mResurrectToPosition = INVALID_POSITION;
2672        }
2673
2674        if (needToRedraw) {
2675            if (selectedView != null) {
2676                positionSelectorLikeFocus(selectedPos, selectedView);
2677                mSelectedTop = selectedView.getTop();
2678            }
2679            if (!awakenScrollBars()) {
2680                invalidate();
2681            }
2682            invokeOnItemScrollListener();
2683            return true;
2684        }
2685
2686        return false;
2687    }
2688
2689    /**
2690     * When selection changes, it is possible that the previously selected or the
2691     * next selected item will change its size.  If so, we need to offset some folks,
2692     * and re-layout the items as appropriate.
2693     *
2694     * @param selectedView The currently selected view (before changing selection).
2695     *   should be <code>null</code> if there was no previous selection.
2696     * @param direction Either {@link android.view.View#FOCUS_UP} or
2697     *        {@link android.view.View#FOCUS_DOWN}.
2698     * @param newSelectedPosition The position of the next selection.
2699     * @param newFocusAssigned whether new focus was assigned.  This matters because
2700     *        when something has focus, we don't want to show selection (ugh).
2701     */
2702    private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2703            boolean newFocusAssigned) {
2704        if (newSelectedPosition == INVALID_POSITION) {
2705            throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2706        }
2707
2708        // whether or not we are moving down or up, we want to preserve the
2709        // top of whatever view is on top:
2710        // - moving down: the view that had selection
2711        // - moving up: the view that is getting selection
2712        View topView;
2713        View bottomView;
2714        int topViewIndex, bottomViewIndex;
2715        boolean topSelected = false;
2716        final int selectedIndex = mSelectedPosition - mFirstPosition;
2717        final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2718        if (direction == View.FOCUS_UP) {
2719            topViewIndex = nextSelectedIndex;
2720            bottomViewIndex = selectedIndex;
2721            topView = getChildAt(topViewIndex);
2722            bottomView = selectedView;
2723            topSelected = true;
2724        } else {
2725            topViewIndex = selectedIndex;
2726            bottomViewIndex = nextSelectedIndex;
2727            topView = selectedView;
2728            bottomView = getChildAt(bottomViewIndex);
2729        }
2730
2731        final int numChildren = getChildCount();
2732
2733        // start with top view: is it changing size?
2734        if (topView != null) {
2735            topView.setSelected(!newFocusAssigned && topSelected);
2736            measureAndAdjustDown(topView, topViewIndex, numChildren);
2737        }
2738
2739        // is the bottom view changing size?
2740        if (bottomView != null) {
2741            bottomView.setSelected(!newFocusAssigned && !topSelected);
2742            measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2743        }
2744    }
2745
2746    /**
2747     * Re-measure a child, and if its height changes, lay it out preserving its
2748     * top, and adjust the children below it appropriately.
2749     * @param child The child
2750     * @param childIndex The view group index of the child.
2751     * @param numChildren The number of children in the view group.
2752     */
2753    private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2754        int oldHeight = child.getHeight();
2755        measureItem(child);
2756        if (child.getMeasuredHeight() != oldHeight) {
2757            // lay out the view, preserving its top
2758            relayoutMeasuredItem(child);
2759
2760            // adjust views below appropriately
2761            final int heightDelta = child.getMeasuredHeight() - oldHeight;
2762            for (int i = childIndex + 1; i < numChildren; i++) {
2763                getChildAt(i).offsetTopAndBottom(heightDelta);
2764            }
2765        }
2766    }
2767
2768    /**
2769     * Measure a particular list child.
2770     * TODO: unify with setUpChild.
2771     * @param child The child.
2772     */
2773    private void measureItem(View child) {
2774        ViewGroup.LayoutParams p = child.getLayoutParams();
2775        if (p == null) {
2776            p = new ViewGroup.LayoutParams(
2777                    ViewGroup.LayoutParams.MATCH_PARENT,
2778                    ViewGroup.LayoutParams.WRAP_CONTENT);
2779        }
2780
2781        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2782                mListPadding.left + mListPadding.right, p.width);
2783        int lpHeight = p.height;
2784        int childHeightSpec;
2785        if (lpHeight > 0) {
2786            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2787        } else {
2788            childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
2789                    MeasureSpec.UNSPECIFIED);
2790        }
2791        child.measure(childWidthSpec, childHeightSpec);
2792    }
2793
2794    /**
2795     * Layout a child that has been measured, preserving its top position.
2796     * TODO: unify with setUpChild.
2797     * @param child The child.
2798     */
2799    private void relayoutMeasuredItem(View child) {
2800        final int w = child.getMeasuredWidth();
2801        final int h = child.getMeasuredHeight();
2802        final int childLeft = mListPadding.left;
2803        final int childRight = childLeft + w;
2804        final int childTop = child.getTop();
2805        final int childBottom = childTop + h;
2806        child.layout(childLeft, childTop, childRight, childBottom);
2807    }
2808
2809    /**
2810     * @return The amount to preview next items when arrow srolling.
2811     */
2812    private int getArrowScrollPreviewLength() {
2813        return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2814    }
2815
2816    /**
2817     * Determine how much we need to scroll in order to get the next selected view
2818     * visible, with a fading edge showing below as applicable.  The amount is
2819     * capped at {@link #getMaxScrollAmount()} .
2820     *
2821     * @param direction either {@link android.view.View#FOCUS_UP} or
2822     *        {@link android.view.View#FOCUS_DOWN}.
2823     * @param nextSelectedPosition The position of the next selection, or
2824     *        {@link #INVALID_POSITION} if there is no next selectable position
2825     * @return The amount to scroll. Note: this is always positive!  Direction
2826     *         needs to be taken into account when actually scrolling.
2827     */
2828    private int amountToScroll(int direction, int nextSelectedPosition) {
2829        final int listBottom = getHeight() - mListPadding.bottom;
2830        final int listTop = mListPadding.top;
2831
2832        int numChildren = getChildCount();
2833
2834        if (direction == View.FOCUS_DOWN) {
2835            int indexToMakeVisible = numChildren - 1;
2836            if (nextSelectedPosition != INVALID_POSITION) {
2837                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2838            }
2839            while (numChildren <= indexToMakeVisible) {
2840                // Child to view is not attached yet.
2841                addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2842                numChildren++;
2843            }
2844            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2845            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2846
2847            int goalBottom = listBottom;
2848            if (positionToMakeVisible < mItemCount - 1) {
2849                goalBottom -= getArrowScrollPreviewLength();
2850            }
2851
2852            if (viewToMakeVisible.getBottom() <= goalBottom) {
2853                // item is fully visible.
2854                return 0;
2855            }
2856
2857            if (nextSelectedPosition != INVALID_POSITION
2858                    && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2859                // item already has enough of it visible, changing selection is good enough
2860                return 0;
2861            }
2862
2863            int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2864
2865            if ((mFirstPosition + numChildren) == mItemCount) {
2866                // last is last in list -> make sure we don't scroll past it
2867                final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2868                amountToScroll = Math.min(amountToScroll, max);
2869            }
2870
2871            return Math.min(amountToScroll, getMaxScrollAmount());
2872        } else {
2873            int indexToMakeVisible = 0;
2874            if (nextSelectedPosition != INVALID_POSITION) {
2875                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2876            }
2877            while (indexToMakeVisible < 0) {
2878                // Child to view is not attached yet.
2879                addViewAbove(getChildAt(0), mFirstPosition);
2880                mFirstPosition--;
2881                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2882            }
2883            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2884            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2885            int goalTop = listTop;
2886            if (positionToMakeVisible > 0) {
2887                goalTop += getArrowScrollPreviewLength();
2888            }
2889            if (viewToMakeVisible.getTop() >= goalTop) {
2890                // item is fully visible.
2891                return 0;
2892            }
2893
2894            if (nextSelectedPosition != INVALID_POSITION &&
2895                    (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2896                // item already has enough of it visible, changing selection is good enough
2897                return 0;
2898            }
2899
2900            int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2901            if (mFirstPosition == 0) {
2902                // first is first in list -> make sure we don't scroll past it
2903                final int max = listTop - getChildAt(0).getTop();
2904                amountToScroll = Math.min(amountToScroll,  max);
2905            }
2906            return Math.min(amountToScroll, getMaxScrollAmount());
2907        }
2908    }
2909
2910    /**
2911     * Holds results of focus aware arrow scrolling.
2912     */
2913    static private class ArrowScrollFocusResult {
2914        private int mSelectedPosition;
2915        private int mAmountToScroll;
2916
2917        /**
2918         * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2919         */
2920        void populate(int selectedPosition, int amountToScroll) {
2921            mSelectedPosition = selectedPosition;
2922            mAmountToScroll = amountToScroll;
2923        }
2924
2925        public int getSelectedPosition() {
2926            return mSelectedPosition;
2927        }
2928
2929        public int getAmountToScroll() {
2930            return mAmountToScroll;
2931        }
2932    }
2933
2934    /**
2935     * @param direction either {@link android.view.View#FOCUS_UP} or
2936     *        {@link android.view.View#FOCUS_DOWN}.
2937     * @return The position of the next selectable position of the views that
2938     *         are currently visible, taking into account the fact that there might
2939     *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
2940     *         selectable view on screen in the given direction.
2941     */
2942    private int lookForSelectablePositionOnScreen(int direction) {
2943        final int firstPosition = mFirstPosition;
2944        if (direction == View.FOCUS_DOWN) {
2945            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2946                    mSelectedPosition + 1 :
2947                    firstPosition;
2948            if (startPos >= mAdapter.getCount()) {
2949                return INVALID_POSITION;
2950            }
2951            if (startPos < firstPosition) {
2952                startPos = firstPosition;
2953            }
2954
2955            final int lastVisiblePos = getLastVisiblePosition();
2956            final ListAdapter adapter = getAdapter();
2957            for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2958                if (adapter.isEnabled(pos)
2959                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2960                    return pos;
2961                }
2962            }
2963        } else {
2964            int last = firstPosition + getChildCount() - 1;
2965            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2966                    mSelectedPosition - 1 :
2967                    firstPosition + getChildCount() - 1;
2968            if (startPos < 0 || startPos >= mAdapter.getCount()) {
2969                return INVALID_POSITION;
2970            }
2971            if (startPos > last) {
2972                startPos = last;
2973            }
2974
2975            final ListAdapter adapter = getAdapter();
2976            for (int pos = startPos; pos >= firstPosition; pos--) {
2977                if (adapter.isEnabled(pos)
2978                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2979                    return pos;
2980                }
2981            }
2982        }
2983        return INVALID_POSITION;
2984    }
2985
2986    /**
2987     * Do an arrow scroll based on focus searching.  If a new view is
2988     * given focus, return the selection delta and amount to scroll via
2989     * an {@link ArrowScrollFocusResult}, otherwise, return null.
2990     *
2991     * @param direction either {@link android.view.View#FOCUS_UP} or
2992     *        {@link android.view.View#FOCUS_DOWN}.
2993     * @return The result if focus has changed, or <code>null</code>.
2994     */
2995    private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2996        final View selectedView = getSelectedView();
2997        View newFocus;
2998        if (selectedView != null && selectedView.hasFocus()) {
2999            View oldFocus = selectedView.findFocus();
3000            newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
3001        } else {
3002            if (direction == View.FOCUS_DOWN) {
3003                final boolean topFadingEdgeShowing = (mFirstPosition > 0);
3004                final int listTop = mListPadding.top +
3005                        (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3006                final int ySearchPoint =
3007                        (selectedView != null && selectedView.getTop() > listTop) ?
3008                                selectedView.getTop() :
3009                                listTop;
3010                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3011            } else {
3012                final boolean bottomFadingEdgeShowing =
3013                        (mFirstPosition + getChildCount() - 1) < mItemCount;
3014                final int listBottom = getHeight() - mListPadding.bottom -
3015                        (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3016                final int ySearchPoint =
3017                        (selectedView != null && selectedView.getBottom() < listBottom) ?
3018                                selectedView.getBottom() :
3019                                listBottom;
3020                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3021            }
3022            newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
3023        }
3024
3025        if (newFocus != null) {
3026            final int positionOfNewFocus = positionOfNewFocus(newFocus);
3027
3028            // if the focus change is in a different new position, make sure
3029            // we aren't jumping over another selectable position
3030            if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
3031                final int selectablePosition = lookForSelectablePositionOnScreen(direction);
3032                if (selectablePosition != INVALID_POSITION &&
3033                        ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
3034                        (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
3035                    return null;
3036                }
3037            }
3038
3039            int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
3040
3041            final int maxScrollAmount = getMaxScrollAmount();
3042            if (focusScroll < maxScrollAmount) {
3043                // not moving too far, safe to give next view focus
3044                newFocus.requestFocus(direction);
3045                mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
3046                return mArrowScrollFocusResult;
3047            } else if (distanceToView(newFocus) < maxScrollAmount){
3048                // Case to consider:
3049                // too far to get entire next focusable on screen, but by going
3050                // max scroll amount, we are getting it at least partially in view,
3051                // so give it focus and scroll the max ammount.
3052                newFocus.requestFocus(direction);
3053                mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
3054                return mArrowScrollFocusResult;
3055            }
3056        }
3057        return null;
3058    }
3059
3060    /**
3061     * @param newFocus The view that would have focus.
3062     * @return the position that contains newFocus
3063     */
3064    private int positionOfNewFocus(View newFocus) {
3065        final int numChildren = getChildCount();
3066        for (int i = 0; i < numChildren; i++) {
3067            final View child = getChildAt(i);
3068            if (isViewAncestorOf(newFocus, child)) {
3069                return mFirstPosition + i;
3070            }
3071        }
3072        throw new IllegalArgumentException("newFocus is not a child of any of the"
3073                + " children of the list!");
3074    }
3075
3076    /**
3077     * Return true if child is an ancestor of parent, (or equal to the parent).
3078     */
3079    private boolean isViewAncestorOf(View child, View parent) {
3080        if (child == parent) {
3081            return true;
3082        }
3083
3084        final ViewParent theParent = child.getParent();
3085        return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
3086    }
3087
3088    /**
3089     * Determine how much we need to scroll in order to get newFocus in view.
3090     * @param direction either {@link android.view.View#FOCUS_UP} or
3091     *        {@link android.view.View#FOCUS_DOWN}.
3092     * @param newFocus The view that would take focus.
3093     * @param positionOfNewFocus The position of the list item containing newFocus
3094     * @return The amount to scroll.  Note: this is always positive!  Direction
3095     *   needs to be taken into account when actually scrolling.
3096     */
3097    private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
3098        int amountToScroll = 0;
3099        newFocus.getDrawingRect(mTempRect);
3100        offsetDescendantRectToMyCoords(newFocus, mTempRect);
3101        if (direction == View.FOCUS_UP) {
3102            if (mTempRect.top < mListPadding.top) {
3103                amountToScroll = mListPadding.top - mTempRect.top;
3104                if (positionOfNewFocus > 0) {
3105                    amountToScroll += getArrowScrollPreviewLength();
3106                }
3107            }
3108        } else {
3109            final int listBottom = getHeight() - mListPadding.bottom;
3110            if (mTempRect.bottom > listBottom) {
3111                amountToScroll = mTempRect.bottom - listBottom;
3112                if (positionOfNewFocus < mItemCount - 1) {
3113                    amountToScroll += getArrowScrollPreviewLength();
3114                }
3115            }
3116        }
3117        return amountToScroll;
3118    }
3119
3120    /**
3121     * Determine the distance to the nearest edge of a view in a particular
3122     * direction.
3123     *
3124     * @param descendant A descendant of this list.
3125     * @return The distance, or 0 if the nearest edge is already on screen.
3126     */
3127    private int distanceToView(View descendant) {
3128        int distance = 0;
3129        descendant.getDrawingRect(mTempRect);
3130        offsetDescendantRectToMyCoords(descendant, mTempRect);
3131        final int listBottom = mBottom - mTop - mListPadding.bottom;
3132        if (mTempRect.bottom < mListPadding.top) {
3133            distance = mListPadding.top - mTempRect.bottom;
3134        } else if (mTempRect.top > listBottom) {
3135            distance = mTempRect.top - listBottom;
3136        }
3137        return distance;
3138    }
3139
3140
3141    /**
3142     * Scroll the children by amount, adding a view at the end and removing
3143     * views that fall off as necessary.
3144     *
3145     * @param amount The amount (positive or negative) to scroll.
3146     */
3147    private void scrollListItemsBy(int amount) {
3148        offsetChildrenTopAndBottom(amount);
3149
3150        final int listBottom = getHeight() - mListPadding.bottom;
3151        final int listTop = mListPadding.top;
3152        final AbsListView.RecycleBin recycleBin = mRecycler;
3153
3154        if (amount < 0) {
3155            // shifted items up
3156
3157            // may need to pan views into the bottom space
3158            int numChildren = getChildCount();
3159            View last = getChildAt(numChildren - 1);
3160            while (last.getBottom() < listBottom) {
3161                final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3162                if (lastVisiblePosition < mItemCount - 1) {
3163                    last = addViewBelow(last, lastVisiblePosition);
3164                    numChildren++;
3165                } else {
3166                    break;
3167                }
3168            }
3169
3170            // may have brought in the last child of the list that is skinnier
3171            // than the fading edge, thereby leaving space at the end.  need
3172            // to shift back
3173            if (last.getBottom() < listBottom) {
3174                offsetChildrenTopAndBottom(listBottom - last.getBottom());
3175            }
3176
3177            // top views may be panned off screen
3178            View first = getChildAt(0);
3179            while (first.getBottom() < listTop) {
3180                AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3181                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3182                    recycleBin.addScrapView(first, mFirstPosition);
3183                }
3184                detachViewFromParent(first);
3185                first = getChildAt(0);
3186                mFirstPosition++;
3187            }
3188        } else {
3189            // shifted items down
3190            View first = getChildAt(0);
3191
3192            // may need to pan views into top
3193            while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3194                first = addViewAbove(first, mFirstPosition);
3195                mFirstPosition--;
3196            }
3197
3198            // may have brought the very first child of the list in too far and
3199            // need to shift it back
3200            if (first.getTop() > listTop) {
3201                offsetChildrenTopAndBottom(listTop - first.getTop());
3202            }
3203
3204            int lastIndex = getChildCount() - 1;
3205            View last = getChildAt(lastIndex);
3206
3207            // bottom view may be panned off screen
3208            while (last.getTop() > listBottom) {
3209                AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3210                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
3211                    recycleBin.addScrapView(last, mFirstPosition+lastIndex);
3212                }
3213                detachViewFromParent(last);
3214                last = getChildAt(--lastIndex);
3215            }
3216        }
3217        recycleBin.fullyDetachScrapViews();
3218        removeUnusedFixedViews(mHeaderViewInfos);
3219        removeUnusedFixedViews(mFooterViewInfos);
3220    }
3221
3222    private View addViewAbove(View theView, int position) {
3223        int abovePosition = position - 1;
3224        View view = obtainView(abovePosition, mIsScrap);
3225        int edgeOfNewChild = theView.getTop() - mDividerHeight;
3226        setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3227                false, mIsScrap[0]);
3228        return view;
3229    }
3230
3231    private View addViewBelow(View theView, int position) {
3232        int belowPosition = position + 1;
3233        View view = obtainView(belowPosition, mIsScrap);
3234        int edgeOfNewChild = theView.getBottom() + mDividerHeight;
3235        setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3236                false, mIsScrap[0]);
3237        return view;
3238    }
3239
3240    /**
3241     * Indicates that the views created by the ListAdapter can contain focusable
3242     * items.
3243     *
3244     * @param itemsCanFocus true if items can get focus, false otherwise
3245     */
3246    public void setItemsCanFocus(boolean itemsCanFocus) {
3247        mItemsCanFocus = itemsCanFocus;
3248        if (!itemsCanFocus) {
3249            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3250        }
3251    }
3252
3253    /**
3254     * @return Whether the views created by the ListAdapter can contain focusable
3255     * items.
3256     */
3257    public boolean getItemsCanFocus() {
3258        return mItemsCanFocus;
3259    }
3260
3261    @Override
3262    public boolean isOpaque() {
3263        boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
3264                hasOpaqueScrollbars()) || super.isOpaque();
3265        if (retValue) {
3266            // only return true if the list items cover the entire area of the view
3267            final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
3268            View first = getChildAt(0);
3269            if (first == null || first.getTop() > listTop) {
3270                return false;
3271            }
3272            final int listBottom = getHeight() -
3273                    (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
3274            View last = getChildAt(getChildCount() - 1);
3275            if (last == null || last.getBottom() < listBottom) {
3276                return false;
3277            }
3278        }
3279        return retValue;
3280    }
3281
3282    @Override
3283    public void setCacheColorHint(int color) {
3284        final boolean opaque = (color >>> 24) == 0xFF;
3285        mIsCacheColorOpaque = opaque;
3286        if (opaque) {
3287            if (mDividerPaint == null) {
3288                mDividerPaint = new Paint();
3289            }
3290            mDividerPaint.setColor(color);
3291        }
3292        super.setCacheColorHint(color);
3293    }
3294
3295    void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3296        final int height = drawable.getMinimumHeight();
3297
3298        canvas.save();
3299        canvas.clipRect(bounds);
3300
3301        final int span = bounds.bottom - bounds.top;
3302        if (span < height) {
3303            bounds.top = bounds.bottom - height;
3304        }
3305
3306        drawable.setBounds(bounds);
3307        drawable.draw(canvas);
3308
3309        canvas.restore();
3310    }
3311
3312    void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3313        final int height = drawable.getMinimumHeight();
3314
3315        canvas.save();
3316        canvas.clipRect(bounds);
3317
3318        final int span = bounds.bottom - bounds.top;
3319        if (span < height) {
3320            bounds.bottom = bounds.top + height;
3321        }
3322
3323        drawable.setBounds(bounds);
3324        drawable.draw(canvas);
3325
3326        canvas.restore();
3327    }
3328
3329    @Override
3330    protected void dispatchDraw(Canvas canvas) {
3331        if (mCachingStarted) {
3332            mCachingActive = true;
3333        }
3334
3335        // Draw the dividers
3336        final int dividerHeight = mDividerHeight;
3337        final Drawable overscrollHeader = mOverScrollHeader;
3338        final Drawable overscrollFooter = mOverScrollFooter;
3339        final boolean drawOverscrollHeader = overscrollHeader != null;
3340        final boolean drawOverscrollFooter = overscrollFooter != null;
3341        final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3342
3343        if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
3344            // Only modify the top and bottom in the loop, we set the left and right here
3345            final Rect bounds = mTempRect;
3346            bounds.left = mPaddingLeft;
3347            bounds.right = mRight - mLeft - mPaddingRight;
3348
3349            final int count = getChildCount();
3350            final int headerCount = mHeaderViewInfos.size();
3351            final int itemCount = mItemCount;
3352            final int footerLimit = (itemCount - mFooterViewInfos.size());
3353            final boolean headerDividers = mHeaderDividersEnabled;
3354            final boolean footerDividers = mFooterDividersEnabled;
3355            final int first = mFirstPosition;
3356            final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3357            final ListAdapter adapter = mAdapter;
3358            // If the list is opaque *and* the background is not, we want to
3359            // fill a rect where the dividers would be for non-selectable items
3360            // If the list is opaque and the background is also opaque, we don't
3361            // need to draw anything since the background will do it for us
3362            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3363
3364            if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
3365                mDividerPaint = new Paint();
3366                mDividerPaint.setColor(getCacheColorHint());
3367            }
3368            final Paint paint = mDividerPaint;
3369
3370            int effectivePaddingTop = 0;
3371            int effectivePaddingBottom = 0;
3372            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3373                effectivePaddingTop = mListPadding.top;
3374                effectivePaddingBottom = mListPadding.bottom;
3375            }
3376
3377            final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
3378            if (!mStackFromBottom) {
3379                int bottom = 0;
3380
3381                // Draw top divider or header for overscroll
3382                final int scrollY = mScrollY;
3383                if (count > 0 && scrollY < 0) {
3384                    if (drawOverscrollHeader) {
3385                        bounds.bottom = 0;
3386                        bounds.top = scrollY;
3387                        drawOverscrollHeader(canvas, overscrollHeader, bounds);
3388                    } else if (drawDividers) {
3389                        bounds.bottom = 0;
3390                        bounds.top = -dividerHeight;
3391                        drawDivider(canvas, bounds, -1);
3392                    }
3393                }
3394
3395                for (int i = 0; i < count; i++) {
3396                    final int itemIndex = (first + i);
3397                    final boolean isHeader = (itemIndex < headerCount);
3398                    final boolean isFooter = (itemIndex >= footerLimit);
3399                    if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3400                        final View child = getChildAt(i);
3401                        bottom = child.getBottom();
3402                        final boolean isLastItem = (i == (count - 1));
3403
3404                        if (drawDividers && (bottom < listBottom)
3405                                && !(drawOverscrollFooter && isLastItem)) {
3406                            final int nextIndex = (itemIndex + 1);
3407                            // Draw dividers between enabled items, headers
3408                            // and/or footers when enabled and requested, and
3409                            // after the last enabled item.
3410                            if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3411                                    && (nextIndex >= headerCount)) && (isLastItem
3412                                    || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3413                                            && (nextIndex < footerLimit)))) {
3414                                bounds.top = bottom;
3415                                bounds.bottom = bottom + dividerHeight;
3416                                drawDivider(canvas, bounds, i);
3417                            } else if (fillForMissingDividers) {
3418                                bounds.top = bottom;
3419                                bounds.bottom = bottom + dividerHeight;
3420                                canvas.drawRect(bounds, paint);
3421                            }
3422                        }
3423                    }
3424                }
3425
3426                final int overFooterBottom = mBottom + mScrollY;
3427                if (drawOverscrollFooter && first + count == itemCount &&
3428                        overFooterBottom > bottom) {
3429                    bounds.top = bottom;
3430                    bounds.bottom = overFooterBottom;
3431                    drawOverscrollFooter(canvas, overscrollFooter, bounds);
3432                }
3433            } else {
3434                int top;
3435
3436                final int scrollY = mScrollY;
3437
3438                if (count > 0 && drawOverscrollHeader) {
3439                    bounds.top = scrollY;
3440                    bounds.bottom = getChildAt(0).getTop();
3441                    drawOverscrollHeader(canvas, overscrollHeader, bounds);
3442                }
3443
3444                final int start = drawOverscrollHeader ? 1 : 0;
3445                for (int i = start; i < count; i++) {
3446                    final int itemIndex = (first + i);
3447                    final boolean isHeader = (itemIndex < headerCount);
3448                    final boolean isFooter = (itemIndex >= footerLimit);
3449                    if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3450                        final View child = getChildAt(i);
3451                        top = child.getTop();
3452                        if (drawDividers && (top > effectivePaddingTop)) {
3453                            final boolean isFirstItem = (i == start);
3454                            final int previousIndex = (itemIndex - 1);
3455                            // Draw dividers between enabled items, headers
3456                            // and/or footers when enabled and requested, and
3457                            // before the first enabled item.
3458                            if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3459                                    && (previousIndex >= headerCount)) && (isFirstItem ||
3460                                    adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3461                                            && (previousIndex < footerLimit)))) {
3462                                bounds.top = top - dividerHeight;
3463                                bounds.bottom = top;
3464                                // Give the method the child ABOVE the divider,
3465                                // so we subtract one from our child position.
3466                                // Give -1 when there is no child above the
3467                                // divider.
3468                                drawDivider(canvas, bounds, i - 1);
3469                            } else if (fillForMissingDividers) {
3470                                bounds.top = top - dividerHeight;
3471                                bounds.bottom = top;
3472                                canvas.drawRect(bounds, paint);
3473                            }
3474                        }
3475                    }
3476                }
3477
3478                if (count > 0 && scrollY > 0) {
3479                    if (drawOverscrollFooter) {
3480                        final int absListBottom = mBottom;
3481                        bounds.top = absListBottom;
3482                        bounds.bottom = absListBottom + scrollY;
3483                        drawOverscrollFooter(canvas, overscrollFooter, bounds);
3484                    } else if (drawDividers) {
3485                        bounds.top = listBottom;
3486                        bounds.bottom = listBottom + dividerHeight;
3487                        drawDivider(canvas, bounds, -1);
3488                    }
3489                }
3490            }
3491        }
3492
3493        // Draw the indicators (these should be drawn above the dividers) and children
3494        super.dispatchDraw(canvas);
3495    }
3496
3497    @Override
3498    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3499        boolean more = super.drawChild(canvas, child, drawingTime);
3500        if (mCachingActive && child.mCachingFailed) {
3501            mCachingActive = false;
3502        }
3503        return more;
3504    }
3505
3506    /**
3507     * Draws a divider for the given child in the given bounds.
3508     *
3509     * @param canvas The canvas to draw to.
3510     * @param bounds The bounds of the divider.
3511     * @param childIndex The index of child (of the View) above the divider.
3512     *            This will be -1 if there is no child above the divider to be
3513     *            drawn.
3514     */
3515    void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3516        // This widget draws the same divider for all children
3517        final Drawable divider = mDivider;
3518
3519        divider.setBounds(bounds);
3520        divider.draw(canvas);
3521    }
3522
3523    /**
3524     * Returns the drawable that will be drawn between each item in the list.
3525     *
3526     * @return the current drawable drawn between list elements
3527     * @attr ref R.styleable#ListView_divider
3528     */
3529    @Nullable
3530    public Drawable getDivider() {
3531        return mDivider;
3532    }
3533
3534    /**
3535     * Sets the drawable that will be drawn between each item in the list.
3536     * <p>
3537     * <strong>Note:</strong> If the drawable does not have an intrinsic
3538     * height, you should also call {@link #setDividerHeight(int)}.
3539     *
3540     * @param divider the drawable to use
3541     * @attr ref R.styleable#ListView_divider
3542     */
3543    public void setDivider(@Nullable Drawable divider) {
3544        if (divider != null) {
3545            mDividerHeight = divider.getIntrinsicHeight();
3546        } else {
3547            mDividerHeight = 0;
3548        }
3549        mDivider = divider;
3550        mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
3551        requestLayout();
3552        invalidate();
3553    }
3554
3555    /**
3556     * @return Returns the height of the divider that will be drawn between each item in the list.
3557     */
3558    public int getDividerHeight() {
3559        return mDividerHeight;
3560    }
3561
3562    /**
3563     * Sets the height of the divider that will be drawn between each item in the list. Calling
3564     * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3565     *
3566     * @param height The new height of the divider in pixels.
3567     */
3568    public void setDividerHeight(int height) {
3569        mDividerHeight = height;
3570        requestLayout();
3571        invalidate();
3572    }
3573
3574    /**
3575     * Enables or disables the drawing of the divider for header views.
3576     *
3577     * @param headerDividersEnabled True to draw the headers, false otherwise.
3578     *
3579     * @see #setFooterDividersEnabled(boolean)
3580     * @see #areHeaderDividersEnabled()
3581     * @see #addHeaderView(android.view.View)
3582     */
3583    public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3584        mHeaderDividersEnabled = headerDividersEnabled;
3585        invalidate();
3586    }
3587
3588    /**
3589     * @return Whether the drawing of the divider for header views is enabled
3590     *
3591     * @see #setHeaderDividersEnabled(boolean)
3592     */
3593    public boolean areHeaderDividersEnabled() {
3594        return mHeaderDividersEnabled;
3595    }
3596
3597    /**
3598     * Enables or disables the drawing of the divider for footer views.
3599     *
3600     * @param footerDividersEnabled True to draw the footers, false otherwise.
3601     *
3602     * @see #setHeaderDividersEnabled(boolean)
3603     * @see #areFooterDividersEnabled()
3604     * @see #addFooterView(android.view.View)
3605     */
3606    public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3607        mFooterDividersEnabled = footerDividersEnabled;
3608        invalidate();
3609    }
3610
3611    /**
3612     * @return Whether the drawing of the divider for footer views is enabled
3613     *
3614     * @see #setFooterDividersEnabled(boolean)
3615     */
3616    public boolean areFooterDividersEnabled() {
3617        return mFooterDividersEnabled;
3618    }
3619
3620    /**
3621     * Sets the drawable that will be drawn above all other list content.
3622     * This area can become visible when the user overscrolls the list.
3623     *
3624     * @param header The drawable to use
3625     */
3626    public void setOverscrollHeader(Drawable header) {
3627        mOverScrollHeader = header;
3628        if (mScrollY < 0) {
3629            invalidate();
3630        }
3631    }
3632
3633    /**
3634     * @return The drawable that will be drawn above all other list content
3635     */
3636    public Drawable getOverscrollHeader() {
3637        return mOverScrollHeader;
3638    }
3639
3640    /**
3641     * Sets the drawable that will be drawn below all other list content.
3642     * This area can become visible when the user overscrolls the list,
3643     * or when the list's content does not fully fill the container area.
3644     *
3645     * @param footer The drawable to use
3646     */
3647    public void setOverscrollFooter(Drawable footer) {
3648        mOverScrollFooter = footer;
3649        invalidate();
3650    }
3651
3652    /**
3653     * @return The drawable that will be drawn below all other list content
3654     */
3655    public Drawable getOverscrollFooter() {
3656        return mOverScrollFooter;
3657    }
3658
3659    @Override
3660    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3661        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3662
3663        final ListAdapter adapter = mAdapter;
3664        int closetChildIndex = -1;
3665        int closestChildTop = 0;
3666        if (adapter != null && gainFocus && previouslyFocusedRect != null) {
3667            previouslyFocusedRect.offset(mScrollX, mScrollY);
3668
3669            // Don't cache the result of getChildCount or mFirstPosition here,
3670            // it could change in layoutChildren.
3671            if (adapter.getCount() < getChildCount() + mFirstPosition) {
3672                mLayoutMode = LAYOUT_NORMAL;
3673                layoutChildren();
3674            }
3675
3676            // figure out which item should be selected based on previously
3677            // focused rect
3678            Rect otherRect = mTempRect;
3679            int minDistance = Integer.MAX_VALUE;
3680            final int childCount = getChildCount();
3681            final int firstPosition = mFirstPosition;
3682
3683            for (int i = 0; i < childCount; i++) {
3684                // only consider selectable views
3685                if (!adapter.isEnabled(firstPosition + i)) {
3686                    continue;
3687                }
3688
3689                View other = getChildAt(i);
3690                other.getDrawingRect(otherRect);
3691                offsetDescendantRectToMyCoords(other, otherRect);
3692                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3693
3694                if (distance < minDistance) {
3695                    minDistance = distance;
3696                    closetChildIndex = i;
3697                    closestChildTop = other.getTop();
3698                }
3699            }
3700        }
3701
3702        if (closetChildIndex >= 0) {
3703            setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
3704        } else {
3705            requestLayout();
3706        }
3707    }
3708
3709
3710    /*
3711     * (non-Javadoc)
3712     *
3713     * Children specified in XML are assumed to be header views. After we have
3714     * parsed them move them out of the children list and into mHeaderViews.
3715     */
3716    @Override
3717    protected void onFinishInflate() {
3718        super.onFinishInflate();
3719
3720        int count = getChildCount();
3721        if (count > 0) {
3722            for (int i = 0; i < count; ++i) {
3723                addHeaderView(getChildAt(i));
3724            }
3725            removeAllViews();
3726        }
3727    }
3728
3729    /* (non-Javadoc)
3730     * @see android.view.View#findViewById(int)
3731     * First look in our children, then in any header and footer views that may be scrolled off.
3732     */
3733    @Override
3734    protected View findViewTraversal(@IdRes int id) {
3735        View v;
3736        v = super.findViewTraversal(id);
3737        if (v == null) {
3738            v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3739            if (v != null) {
3740                return v;
3741            }
3742            v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3743            if (v != null) {
3744                return v;
3745            }
3746        }
3747        return v;
3748    }
3749
3750    /* (non-Javadoc)
3751     *
3752     * Look in the passed in list of headers or footers for the view.
3753     */
3754    View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3755        if (where != null) {
3756            int len = where.size();
3757            View v;
3758
3759            for (int i = 0; i < len; i++) {
3760                v = where.get(i).view;
3761
3762                if (!v.isRootNamespace()) {
3763                    v = v.findViewById(id);
3764
3765                    if (v != null) {
3766                        return v;
3767                    }
3768                }
3769            }
3770        }
3771        return null;
3772    }
3773
3774    /* (non-Javadoc)
3775     * @see android.view.View#findViewWithTag(Object)
3776     * First look in our children, then in any header and footer views that may be scrolled off.
3777     */
3778    @Override
3779    protected View findViewWithTagTraversal(Object tag) {
3780        View v;
3781        v = super.findViewWithTagTraversal(tag);
3782        if (v == null) {
3783            v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
3784            if (v != null) {
3785                return v;
3786            }
3787
3788            v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
3789            if (v != null) {
3790                return v;
3791            }
3792        }
3793        return v;
3794    }
3795
3796    /* (non-Javadoc)
3797     *
3798     * Look in the passed in list of headers or footers for the view with the tag.
3799     */
3800    View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
3801        if (where != null) {
3802            int len = where.size();
3803            View v;
3804
3805            for (int i = 0; i < len; i++) {
3806                v = where.get(i).view;
3807
3808                if (!v.isRootNamespace()) {
3809                    v = v.findViewWithTag(tag);
3810
3811                    if (v != null) {
3812                        return v;
3813                    }
3814                }
3815            }
3816        }
3817        return null;
3818    }
3819
3820    /**
3821     * @hide
3822     * @see android.view.View#findViewByPredicate(Predicate)
3823     * First look in our children, then in any header and footer views that may be scrolled off.
3824     */
3825    @Override
3826    protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
3827        View v;
3828        v = super.findViewByPredicateTraversal(predicate, childToSkip);
3829        if (v == null) {
3830            v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
3831            if (v != null) {
3832                return v;
3833            }
3834
3835            v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
3836            if (v != null) {
3837                return v;
3838            }
3839        }
3840        return v;
3841    }
3842
3843    /* (non-Javadoc)
3844     *
3845     * Look in the passed in list of headers or footers for the first view that matches
3846     * the predicate.
3847     */
3848    View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
3849            Predicate<View> predicate, View childToSkip) {
3850        if (where != null) {
3851            int len = where.size();
3852            View v;
3853
3854            for (int i = 0; i < len; i++) {
3855                v = where.get(i).view;
3856
3857                if (v != childToSkip && !v.isRootNamespace()) {
3858                    v = v.findViewByPredicate(predicate);
3859
3860                    if (v != null) {
3861                        return v;
3862                    }
3863                }
3864            }
3865        }
3866        return null;
3867    }
3868
3869    /**
3870     * Returns the set of checked items ids. The result is only valid if the
3871     * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3872     *
3873     * @return A new array which contains the id of each checked item in the
3874     *         list.
3875     *
3876     * @deprecated Use {@link #getCheckedItemIds()} instead.
3877     */
3878    @Deprecated
3879    public long[] getCheckItemIds() {
3880        // Use new behavior that correctly handles stable ID mapping.
3881        if (mAdapter != null && mAdapter.hasStableIds()) {
3882            return getCheckedItemIds();
3883        }
3884
3885        // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3886        // Fall back to it to support legacy apps.
3887        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3888            final SparseBooleanArray states = mCheckStates;
3889            final int count = states.size();
3890            final long[] ids = new long[count];
3891            final ListAdapter adapter = mAdapter;
3892
3893            int checkedCount = 0;
3894            for (int i = 0; i < count; i++) {
3895                if (states.valueAt(i)) {
3896                    ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3897                }
3898            }
3899
3900            // Trim array if needed. mCheckStates may contain false values
3901            // resulting in checkedCount being smaller than count.
3902            if (checkedCount == count) {
3903                return ids;
3904            } else {
3905                final long[] result = new long[checkedCount];
3906                System.arraycopy(ids, 0, result, 0, checkedCount);
3907
3908                return result;
3909            }
3910        }
3911        return new long[0];
3912    }
3913
3914    @Override
3915    int getHeightForPosition(int position) {
3916        final int height = super.getHeightForPosition(position);
3917        if (shouldAdjustHeightForDivider(position)) {
3918            return height + mDividerHeight;
3919        }
3920        return height;
3921    }
3922
3923    private boolean shouldAdjustHeightForDivider(int itemIndex) {
3924        final int dividerHeight = mDividerHeight;
3925        final Drawable overscrollHeader = mOverScrollHeader;
3926        final Drawable overscrollFooter = mOverScrollFooter;
3927        final boolean drawOverscrollHeader = overscrollHeader != null;
3928        final boolean drawOverscrollFooter = overscrollFooter != null;
3929        final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3930
3931        if (drawDividers) {
3932            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3933            final int itemCount = mItemCount;
3934            final int headerCount = mHeaderViewInfos.size();
3935            final int footerLimit = (itemCount - mFooterViewInfos.size());
3936            final boolean isHeader = (itemIndex < headerCount);
3937            final boolean isFooter = (itemIndex >= footerLimit);
3938            final boolean headerDividers = mHeaderDividersEnabled;
3939            final boolean footerDividers = mFooterDividersEnabled;
3940            if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3941                final ListAdapter adapter = mAdapter;
3942                if (!mStackFromBottom) {
3943                    final boolean isLastItem = (itemIndex == (itemCount - 1));
3944                    if (!drawOverscrollFooter || !isLastItem) {
3945                        final int nextIndex = itemIndex + 1;
3946                        // Draw dividers between enabled items, headers
3947                        // and/or footers when enabled and requested, and
3948                        // after the last enabled item.
3949                        if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3950                                && (nextIndex >= headerCount)) && (isLastItem
3951                                || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3952                                                && (nextIndex < footerLimit)))) {
3953                            return true;
3954                        } else if (fillForMissingDividers) {
3955                            return true;
3956                        }
3957                    }
3958                } else {
3959                    final int start = drawOverscrollHeader ? 1 : 0;
3960                    final boolean isFirstItem = (itemIndex == start);
3961                    if (!isFirstItem) {
3962                        final int previousIndex = (itemIndex - 1);
3963                        // Draw dividers between enabled items, headers
3964                        // and/or footers when enabled and requested, and
3965                        // before the first enabled item.
3966                        if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3967                                && (previousIndex >= headerCount)) && (isFirstItem ||
3968                                adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3969                                        && (previousIndex < footerLimit)))) {
3970                            return true;
3971                        } else if (fillForMissingDividers) {
3972                            return true;
3973                        }
3974                    }
3975                }
3976            }
3977        }
3978
3979        return false;
3980    }
3981
3982    @Override
3983    public CharSequence getAccessibilityClassName() {
3984        return ListView.class.getName();
3985    }
3986
3987    /** @hide */
3988    @Override
3989    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3990        super.onInitializeAccessibilityNodeInfoInternal(info);
3991
3992        final int rowsCount = getCount();
3993        final int selectionMode = getSelectionModeForAccessibility();
3994        final CollectionInfo collectionInfo = CollectionInfo.obtain(
3995                rowsCount, 1, false, selectionMode);
3996        info.setCollectionInfo(collectionInfo);
3997
3998        if (rowsCount > 0) {
3999            info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
4000        }
4001    }
4002
4003    /** @hide */
4004    @Override
4005    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4006        if (super.performAccessibilityActionInternal(action, arguments)) {
4007            return true;
4008        }
4009
4010        switch (action) {
4011            case R.id.accessibilityActionScrollToPosition: {
4012                final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
4013                final int position = Math.min(row, getCount() - 1);
4014                if (row >= 0) {
4015                    // The accessibility service gets data asynchronously, so
4016                    // we'll be a little lenient by clamping the last position.
4017                    smoothScrollToPosition(position);
4018                    return true;
4019                }
4020            } break;
4021        }
4022
4023        return false;
4024    }
4025
4026    @Override
4027    public void onInitializeAccessibilityNodeInfoForItem(
4028            View view, int position, AccessibilityNodeInfo info) {
4029        super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
4030
4031        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
4032        final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
4033        final boolean isSelected = isItemChecked(position);
4034        final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
4035                position, 1, 0, 1, isHeading, isSelected);
4036        info.setCollectionItemInfo(itemInfo);
4037    }
4038
4039    /** @hide */
4040    @Override
4041    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
4042        super.encodeProperties(encoder);
4043
4044        encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
4045    }
4046}
4047