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