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