ListView.java revision fb60386b46d0c6216c765c10bd33ac42ca780917
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            // Don't put header or footer views into the Recycler. Those are
1539            // already cached in mHeaderViews;
1540            if (dataChanged) {
1541                for (int i = 0; i < childCount; i++) {
1542                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
1543                    if (ViewDebug.TRACE_RECYCLER) {
1544                        ViewDebug.trace(getChildAt(i),
1545                                ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
1546                    }
1547                }
1548            } else {
1549                recycleBin.fillActiveViews(childCount, firstPosition);
1550            }
1551
1552            // take focus back to us temporarily to avoid the eventual
1553            // call to clear focus when removing the focused child below
1554            // from messing things up when ViewRoot assigns focus back
1555            // to someone else
1556            final View focusedChild = getFocusedChild();
1557            if (focusedChild != null) {
1558                // TODO: in some cases focusedChild.getParent() == null
1559
1560                // we can remember the focused view to restore after relayout if the
1561                // data hasn't changed, or if the focused position is a header or footer
1562                if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
1563                    focusLayoutRestoreDirectChild = focusedChild;
1564                    // remember the specific view that had focus
1565                    focusLayoutRestoreView = findFocus();
1566                    if (focusLayoutRestoreView != null) {
1567                        // tell it we are going to mess with it
1568                        focusLayoutRestoreView.onStartTemporaryDetach();
1569                    }
1570                }
1571                requestFocus();
1572            }
1573
1574            // Clear out old views
1575            detachAllViewsFromParent();
1576
1577            switch (mLayoutMode) {
1578            case LAYOUT_SET_SELECTION:
1579                if (newSel != null) {
1580                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1581                } else {
1582                    sel = fillFromMiddle(childrenTop, childrenBottom);
1583                }
1584                break;
1585            case LAYOUT_SYNC:
1586                sel = fillSpecific(mSyncPosition, mSpecificTop);
1587                break;
1588            case LAYOUT_FORCE_BOTTOM:
1589                sel = fillUp(mItemCount - 1, childrenBottom);
1590                adjustViewsUpOrDown();
1591                break;
1592            case LAYOUT_FORCE_TOP:
1593                mFirstPosition = 0;
1594                sel = fillFromTop(childrenTop);
1595                adjustViewsUpOrDown();
1596                break;
1597            case LAYOUT_SPECIFIC:
1598                sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
1599                break;
1600            case LAYOUT_MOVE_SELECTION:
1601                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1602                break;
1603            default:
1604                if (childCount == 0) {
1605                    if (!mStackFromBottom) {
1606                        final int position = lookForSelectablePosition(0, true);
1607                        setSelectedPositionInt(position);
1608                        sel = fillFromTop(childrenTop);
1609                    } else {
1610                        final int position = lookForSelectablePosition(mItemCount - 1, false);
1611                        setSelectedPositionInt(position);
1612                        sel = fillUp(mItemCount - 1, childrenBottom);
1613                    }
1614                } else {
1615                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1616                        sel = fillSpecific(mSelectedPosition,
1617                                oldSel == null ? childrenTop : oldSel.getTop());
1618                    } else if (mFirstPosition < mItemCount) {
1619                        sel = fillSpecific(mFirstPosition,
1620                                oldFirst == null ? childrenTop : oldFirst.getTop());
1621                    } else {
1622                        sel = fillSpecific(0, childrenTop);
1623                    }
1624                }
1625                break;
1626            }
1627
1628            // Flush any cached views that did not get reused above
1629            recycleBin.scrapActiveViews();
1630
1631            if (sel != null) {
1632                // the current selected item should get focus if items
1633                // are focusable
1634                if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1635                    final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1636                            focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1637                    if (!focusWasTaken) {
1638                        // selected item didn't take focus, fine, but still want
1639                        // to make sure something else outside of the selected view
1640                        // has focus
1641                        final View focused = getFocusedChild();
1642                        if (focused != null) {
1643                            focused.clearFocus();
1644                        }
1645                        positionSelector(INVALID_POSITION, sel);
1646                    } else {
1647                        sel.setSelected(false);
1648                        mSelectorRect.setEmpty();
1649                    }
1650                } else {
1651                    positionSelector(INVALID_POSITION, sel);
1652                }
1653                mSelectedTop = sel.getTop();
1654            } else {
1655                if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1656                    View child = getChildAt(mMotionPosition - mFirstPosition);
1657                    if (child != null) positionSelector(mMotionPosition, child);
1658                } else {
1659                    mSelectedTop = 0;
1660                    mSelectorRect.setEmpty();
1661                }
1662
1663                // even if there is not selected position, we may need to restore
1664                // focus (i.e. something focusable in touch mode)
1665                if (hasFocus() && focusLayoutRestoreView != null) {
1666                    focusLayoutRestoreView.requestFocus();
1667                }
1668            }
1669
1670            // tell focus view we are done mucking with it, if it is still in
1671            // our view hierarchy.
1672            if (focusLayoutRestoreView != null
1673                    && focusLayoutRestoreView.getWindowToken() != null) {
1674                focusLayoutRestoreView.onFinishTemporaryDetach();
1675            }
1676
1677            mLayoutMode = LAYOUT_NORMAL;
1678            mDataChanged = false;
1679            mNeedSync = false;
1680            setNextSelectedPositionInt(mSelectedPosition);
1681
1682            updateScrollIndicators();
1683
1684            if (mItemCount > 0) {
1685                checkSelectionChanged();
1686            }
1687
1688            invokeOnItemScrollListener();
1689        } finally {
1690            if (!blockLayoutRequests) {
1691                mBlockLayoutRequests = false;
1692            }
1693        }
1694    }
1695
1696    /**
1697     * @param child a direct child of this list.
1698     * @return Whether child is a header or footer view.
1699     */
1700    private boolean isDirectChildHeaderOrFooter(View child) {
1701
1702        final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1703        final int numHeaders = headers.size();
1704        for (int i = 0; i < numHeaders; i++) {
1705            if (child == headers.get(i).view) {
1706                return true;
1707            }
1708        }
1709        final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1710        final int numFooters = footers.size();
1711        for (int i = 0; i < numFooters; i++) {
1712            if (child == footers.get(i).view) {
1713                return true;
1714            }
1715        }
1716        return false;
1717    }
1718
1719    /**
1720     * Obtain the view and add it to our list of children. The view can be made
1721     * fresh, converted from an unused view, or used as is if it was in the
1722     * recycle bin.
1723     *
1724     * @param position Logical position in the list
1725     * @param y Top or bottom edge of the view to add
1726     * @param flow If flow is true, align top edge to y. If false, align bottom
1727     *        edge to y.
1728     * @param childrenLeft Left edge where children should be positioned
1729     * @param selected Is this position selected?
1730     * @return View that was added
1731     */
1732    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1733            boolean selected) {
1734        View child;
1735
1736
1737        if (!mDataChanged) {
1738            // Try to use an existing view for this position
1739            child = mRecycler.getActiveView(position);
1740            if (child != null) {
1741                if (ViewDebug.TRACE_RECYCLER) {
1742                    ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
1743                            position, getChildCount());
1744                }
1745
1746                // Found it -- we're using an existing child
1747                // This just needs to be positioned
1748                setupChild(child, position, y, flow, childrenLeft, selected, true);
1749
1750                return child;
1751            }
1752        }
1753
1754        // Make a new view for this position, or convert an unused view if possible
1755        child = obtainView(position, mIsScrap);
1756
1757        // This needs to be positioned and measured
1758        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
1759
1760        return child;
1761    }
1762
1763    /**
1764     * Add a view as a child and make sure it is measured (if necessary) and
1765     * positioned properly.
1766     *
1767     * @param child The view to add
1768     * @param position The position of this child
1769     * @param y The y position relative to which this view will be positioned
1770     * @param flowDown If true, align top edge to y. If false, align bottom
1771     *        edge to y.
1772     * @param childrenLeft Left edge where children should be positioned
1773     * @param selected Is this position selected?
1774     * @param recycled Has this view been pulled from the recycle bin? If so it
1775     *        does not need to be remeasured.
1776     */
1777    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1778            boolean selected, boolean recycled) {
1779        final boolean isSelected = selected && shouldShowSelector();
1780        final boolean updateChildSelected = isSelected != child.isSelected();
1781        final int mode = mTouchMode;
1782        final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1783                mMotionPosition == position;
1784        final boolean updateChildPressed = isPressed != child.isPressed();
1785        final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1786
1787        // Respect layout params that are already in the view. Otherwise make some up...
1788        // noinspection unchecked
1789        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1790        if (p == null) {
1791            p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1792                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
1793        }
1794        p.viewType = mAdapter.getItemViewType(position);
1795
1796        if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
1797                p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
1798            attachViewToParent(child, flowDown ? -1 : 0, p);
1799        } else {
1800            p.forceAdd = false;
1801            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1802                p.recycledHeaderFooter = true;
1803            }
1804            addViewInLayout(child, flowDown ? -1 : 0, p, true);
1805        }
1806
1807        if (updateChildSelected) {
1808            child.setSelected(isSelected);
1809        }
1810
1811        if (updateChildPressed) {
1812            child.setPressed(isPressed);
1813        }
1814
1815        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1816            if (child instanceof Checkable) {
1817                ((Checkable) child).setChecked(mCheckStates.get(position));
1818            } else if (getContext().getApplicationInfo().targetSdkVersion
1819                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1820                child.setActivated(mCheckStates.get(position));
1821            }
1822        }
1823
1824        if (needToMeasure) {
1825            int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
1826                    mListPadding.left + mListPadding.right, p.width);
1827            int lpHeight = p.height;
1828            int childHeightSpec;
1829            if (lpHeight > 0) {
1830                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1831            } else {
1832                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1833            }
1834            child.measure(childWidthSpec, childHeightSpec);
1835        } else {
1836            cleanupLayoutState(child);
1837        }
1838
1839        final int w = child.getMeasuredWidth();
1840        final int h = child.getMeasuredHeight();
1841        final int childTop = flowDown ? y : y - h;
1842
1843        if (needToMeasure) {
1844            final int childRight = childrenLeft + w;
1845            final int childBottom = childTop + h;
1846            child.layout(childrenLeft, childTop, childRight, childBottom);
1847        } else {
1848            child.offsetLeftAndRight(childrenLeft - child.getLeft());
1849            child.offsetTopAndBottom(childTop - child.getTop());
1850        }
1851
1852        if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1853            child.setDrawingCacheEnabled(true);
1854        }
1855
1856        if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1857                != position) {
1858            child.jumpDrawablesToCurrentState();
1859        }
1860    }
1861
1862    @Override
1863    protected boolean canAnimate() {
1864        return super.canAnimate() && mItemCount > 0;
1865    }
1866
1867    /**
1868     * Sets the currently selected item. If in touch mode, the item will not be selected
1869     * but it will still be positioned appropriately. If the specified selection position
1870     * is less than 0, then the item at position 0 will be selected.
1871     *
1872     * @param position Index (starting at 0) of the data item to be selected.
1873     */
1874    @Override
1875    public void setSelection(int position) {
1876        setSelectionFromTop(position, 0);
1877    }
1878
1879    /**
1880     * Sets the selected item and positions the selection y pixels from the top edge
1881     * of the ListView. (If in touch mode, the item will not be selected but it will
1882     * still be positioned appropriately.)
1883     *
1884     * @param position Index (starting at 0) of the data item to be selected.
1885     * @param y The distance from the top edge of the ListView (plus padding) that the
1886     *        item will be positioned.
1887     */
1888    public void setSelectionFromTop(int position, int y) {
1889        if (mAdapter == null) {
1890            return;
1891        }
1892
1893        if (!isInTouchMode()) {
1894            position = lookForSelectablePosition(position, true);
1895            if (position >= 0) {
1896                setNextSelectedPositionInt(position);
1897            }
1898        } else {
1899            mResurrectToPosition = position;
1900        }
1901
1902        if (position >= 0) {
1903            mLayoutMode = LAYOUT_SPECIFIC;
1904            mSpecificTop = mListPadding.top + y;
1905
1906            if (mNeedSync) {
1907                mSyncPosition = position;
1908                mSyncRowId = mAdapter.getItemId(position);
1909            }
1910
1911            requestLayout();
1912        }
1913    }
1914
1915    /**
1916     * Makes the item at the supplied position selected.
1917     *
1918     * @param position the position of the item to select
1919     */
1920    @Override
1921    void setSelectionInt(int position) {
1922        setNextSelectedPositionInt(position);
1923        boolean awakeScrollbars = false;
1924
1925        final int selectedPosition = mSelectedPosition;
1926
1927        if (selectedPosition >= 0) {
1928            if (position == selectedPosition - 1) {
1929                awakeScrollbars = true;
1930            } else if (position == selectedPosition + 1) {
1931                awakeScrollbars = true;
1932            }
1933        }
1934
1935        layoutChildren();
1936
1937        if (awakeScrollbars) {
1938            awakenScrollBars();
1939        }
1940    }
1941
1942    /**
1943     * Find a position that can be selected (i.e., is not a separator).
1944     *
1945     * @param position The starting position to look at.
1946     * @param lookDown Whether to look down for other positions.
1947     * @return The next selectable position starting at position and then searching either up or
1948     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
1949     */
1950    @Override
1951    int lookForSelectablePosition(int position, boolean lookDown) {
1952        final ListAdapter adapter = mAdapter;
1953        if (adapter == null || isInTouchMode()) {
1954            return INVALID_POSITION;
1955        }
1956
1957        final int count = adapter.getCount();
1958        if (!mAreAllItemsSelectable) {
1959            if (lookDown) {
1960                position = Math.max(0, position);
1961                while (position < count && !adapter.isEnabled(position)) {
1962                    position++;
1963                }
1964            } else {
1965                position = Math.min(position, count - 1);
1966                while (position >= 0 && !adapter.isEnabled(position)) {
1967                    position--;
1968                }
1969            }
1970
1971            if (position < 0 || position >= count) {
1972                return INVALID_POSITION;
1973            }
1974            return position;
1975        } else {
1976            if (position < 0 || position >= count) {
1977                return INVALID_POSITION;
1978            }
1979            return position;
1980        }
1981    }
1982
1983    @Override
1984    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1985        boolean populated = super.dispatchPopulateAccessibilityEvent(event);
1986
1987        // If the item count is less than 15 then subtract disabled items from the count and
1988        // position. Otherwise ignore disabled items.
1989        if (!populated) {
1990            int itemCount = 0;
1991            int currentItemIndex = getSelectedItemPosition();
1992
1993            ListAdapter adapter = getAdapter();
1994            if (adapter != null) {
1995                final int count = adapter.getCount();
1996                if (count < 15) {
1997                    for (int i = 0; i < count; i++) {
1998                        if (adapter.isEnabled(i)) {
1999                            itemCount++;
2000                        } else if (i <= currentItemIndex) {
2001                            currentItemIndex--;
2002                        }
2003                    }
2004                } else {
2005                    itemCount = count;
2006                }
2007            }
2008
2009            event.setItemCount(itemCount);
2010            event.setCurrentItemIndex(currentItemIndex);
2011        }
2012
2013        return populated;
2014    }
2015
2016    /**
2017     * setSelectionAfterHeaderView set the selection to be the first list item
2018     * after the header views.
2019     */
2020    public void setSelectionAfterHeaderView() {
2021        final int count = mHeaderViewInfos.size();
2022        if (count > 0) {
2023            mNextSelectedPosition = 0;
2024            return;
2025        }
2026
2027        if (mAdapter != null) {
2028            setSelection(count);
2029        } else {
2030            mNextSelectedPosition = count;
2031            mLayoutMode = LAYOUT_SET_SELECTION;
2032        }
2033
2034    }
2035
2036    @Override
2037    public boolean dispatchKeyEvent(KeyEvent event) {
2038        // Dispatch in the normal way
2039        boolean handled = super.dispatchKeyEvent(event);
2040        if (!handled) {
2041            // If we didn't handle it...
2042            View focused = getFocusedChild();
2043            if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2044                // ... and our focused child didn't handle it
2045                // ... give it to ourselves so we can scroll if necessary
2046                handled = onKeyDown(event.getKeyCode(), event);
2047            }
2048        }
2049        return handled;
2050    }
2051
2052    @Override
2053    public boolean onKeyDown(int keyCode, KeyEvent event) {
2054        return commonKey(keyCode, 1, event);
2055    }
2056
2057    @Override
2058    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2059        return commonKey(keyCode, repeatCount, event);
2060    }
2061
2062    @Override
2063    public boolean onKeyUp(int keyCode, KeyEvent event) {
2064        return commonKey(keyCode, 1, event);
2065    }
2066
2067    private boolean commonKey(int keyCode, int count, KeyEvent event) {
2068        if (mAdapter == null) {
2069            return false;
2070        }
2071
2072        if (mDataChanged) {
2073            layoutChildren();
2074        }
2075
2076        boolean handled = false;
2077        int action = event.getAction();
2078
2079        if (action != KeyEvent.ACTION_UP) {
2080            if (mSelectedPosition < 0) {
2081                switch (keyCode) {
2082                case KeyEvent.KEYCODE_DPAD_UP:
2083                case KeyEvent.KEYCODE_DPAD_DOWN:
2084                case KeyEvent.KEYCODE_DPAD_CENTER:
2085                case KeyEvent.KEYCODE_ENTER:
2086                case KeyEvent.KEYCODE_SPACE:
2087                    if (resurrectSelection()) {
2088                        return true;
2089                    }
2090                }
2091            }
2092            switch (keyCode) {
2093            case KeyEvent.KEYCODE_DPAD_UP:
2094                if (!event.isAltPressed()) {
2095                    while (count > 0) {
2096                        handled = arrowScroll(FOCUS_UP);
2097                        count--;
2098                    }
2099                } else {
2100                    handled = fullScroll(FOCUS_UP);
2101                }
2102                break;
2103
2104            case KeyEvent.KEYCODE_DPAD_DOWN:
2105                if (!event.isAltPressed()) {
2106                    while (count > 0) {
2107                        handled = arrowScroll(FOCUS_DOWN);
2108                        count--;
2109                    }
2110                } else {
2111                    handled = fullScroll(FOCUS_DOWN);
2112                }
2113                break;
2114
2115            case KeyEvent.KEYCODE_DPAD_LEFT:
2116                handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2117                break;
2118            case KeyEvent.KEYCODE_DPAD_RIGHT:
2119                handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2120                break;
2121
2122            case KeyEvent.KEYCODE_DPAD_CENTER:
2123            case KeyEvent.KEYCODE_ENTER:
2124                if (mItemCount > 0 && event.getRepeatCount() == 0) {
2125                    keyPressed();
2126                }
2127                handled = true;
2128                break;
2129
2130            case KeyEvent.KEYCODE_SPACE:
2131                if (mPopup == null || !mPopup.isShowing()) {
2132                    if (!event.isShiftPressed()) {
2133                        pageScroll(FOCUS_DOWN);
2134                    } else {
2135                        pageScroll(FOCUS_UP);
2136                    }
2137                    handled = true;
2138                }
2139                break;
2140            }
2141        }
2142
2143        if (!handled) {
2144            handled = sendToTextFilter(keyCode, count, event);
2145        }
2146
2147        if (handled) {
2148            return true;
2149        } else {
2150            switch (action) {
2151                case KeyEvent.ACTION_DOWN:
2152                    return super.onKeyDown(keyCode, event);
2153
2154                case KeyEvent.ACTION_UP:
2155                    return super.onKeyUp(keyCode, event);
2156
2157                case KeyEvent.ACTION_MULTIPLE:
2158                    return super.onKeyMultiple(keyCode, count, event);
2159
2160                default: // shouldn't happen
2161                    return false;
2162            }
2163        }
2164    }
2165
2166    /**
2167     * Scrolls up or down by the number of items currently present on screen.
2168     *
2169     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2170     * @return whether selection was moved
2171     */
2172    boolean pageScroll(int direction) {
2173        int nextPage = -1;
2174        boolean down = false;
2175
2176        if (direction == FOCUS_UP) {
2177            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2178        } else if (direction == FOCUS_DOWN) {
2179            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2180            down = true;
2181        }
2182
2183        if (nextPage >= 0) {
2184            int position = lookForSelectablePosition(nextPage, down);
2185            if (position >= 0) {
2186                mLayoutMode = LAYOUT_SPECIFIC;
2187                mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2188
2189                if (down && position > mItemCount - getChildCount()) {
2190                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2191                }
2192
2193                if (!down && position < getChildCount()) {
2194                    mLayoutMode = LAYOUT_FORCE_TOP;
2195                }
2196
2197                setSelectionInt(position);
2198                invokeOnItemScrollListener();
2199                if (!awakenScrollBars()) {
2200                    invalidate();
2201                }
2202
2203                return true;
2204            }
2205        }
2206
2207        return false;
2208    }
2209
2210    /**
2211     * Go to the last or first item if possible (not worrying about panning across or navigating
2212     * within the internal focus of the currently selected item.)
2213     *
2214     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2215     *
2216     * @return whether selection was moved
2217     */
2218    boolean fullScroll(int direction) {
2219        boolean moved = false;
2220        if (direction == FOCUS_UP) {
2221            if (mSelectedPosition != 0) {
2222                int position = lookForSelectablePosition(0, true);
2223                if (position >= 0) {
2224                    mLayoutMode = LAYOUT_FORCE_TOP;
2225                    setSelectionInt(position);
2226                    invokeOnItemScrollListener();
2227                }
2228                moved = true;
2229            }
2230        } else if (direction == FOCUS_DOWN) {
2231            if (mSelectedPosition < mItemCount - 1) {
2232                int position = lookForSelectablePosition(mItemCount - 1, true);
2233                if (position >= 0) {
2234                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2235                    setSelectionInt(position);
2236                    invokeOnItemScrollListener();
2237                }
2238                moved = true;
2239            }
2240        }
2241
2242        if (moved && !awakenScrollBars()) {
2243            awakenScrollBars();
2244            invalidate();
2245        }
2246
2247        return moved;
2248    }
2249
2250    /**
2251     * To avoid horizontal focus searches changing the selected item, we
2252     * manually focus search within the selected item (as applicable), and
2253     * prevent focus from jumping to something within another item.
2254     * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2255     * @return Whether this consumes the key event.
2256     */
2257    private boolean handleHorizontalFocusWithinListItem(int direction) {
2258        if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
2259            throw new IllegalArgumentException("direction must be one of"
2260                    + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
2261        }
2262
2263        final int numChildren = getChildCount();
2264        if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2265            final View selectedView = getSelectedView();
2266            if (selectedView != null && selectedView.hasFocus() &&
2267                    selectedView instanceof ViewGroup) {
2268
2269                final View currentFocus = selectedView.findFocus();
2270                final View nextFocus = FocusFinder.getInstance().findNextFocus(
2271                        (ViewGroup) selectedView, currentFocus, direction);
2272                if (nextFocus != null) {
2273                    // do the math to get interesting rect in next focus' coordinates
2274                    currentFocus.getFocusedRect(mTempRect);
2275                    offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2276                    offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2277                    if (nextFocus.requestFocus(direction, mTempRect)) {
2278                        return true;
2279                    }
2280                }
2281                // we are blocking the key from being handled (by returning true)
2282                // if the global result is going to be some other view within this
2283                // list.  this is to acheive the overall goal of having
2284                // horizontal d-pad navigation remain in the current item.
2285                final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2286                        (ViewGroup) getRootView(), currentFocus, direction);
2287                if (globalNextFocus != null) {
2288                    return isViewAncestorOf(globalNextFocus, this);
2289                }
2290            }
2291        }
2292        return false;
2293    }
2294
2295    /**
2296     * Scrolls to the next or previous item if possible.
2297     *
2298     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2299     *
2300     * @return whether selection was moved
2301     */
2302    boolean arrowScroll(int direction) {
2303        try {
2304            mInLayout = true;
2305            final boolean handled = arrowScrollImpl(direction);
2306            if (handled) {
2307                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2308            }
2309            return handled;
2310        } finally {
2311            mInLayout = false;
2312        }
2313    }
2314
2315    /**
2316     * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
2317     * whether there are focusable items etc.
2318     *
2319     * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2320     * @return Whether any scrolling, selection or focus change occured.
2321     */
2322    private boolean arrowScrollImpl(int direction) {
2323        if (getChildCount() <= 0) {
2324            return false;
2325        }
2326
2327        View selectedView = getSelectedView();
2328        int selectedPos = mSelectedPosition;
2329
2330        int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
2331        int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2332
2333        // if we are moving focus, we may OVERRIDE the default behavior
2334        final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2335        if (focusResult != null) {
2336            nextSelectedPosition = focusResult.getSelectedPosition();
2337            amountToScroll = focusResult.getAmountToScroll();
2338        }
2339
2340        boolean needToRedraw = focusResult != null;
2341        if (nextSelectedPosition != INVALID_POSITION) {
2342            handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2343            setSelectedPositionInt(nextSelectedPosition);
2344            setNextSelectedPositionInt(nextSelectedPosition);
2345            selectedView = getSelectedView();
2346            selectedPos = nextSelectedPosition;
2347            if (mItemsCanFocus && focusResult == null) {
2348                // there was no new view found to take focus, make sure we
2349                // don't leave focus with the old selection
2350                final View focused = getFocusedChild();
2351                if (focused != null) {
2352                    focused.clearFocus();
2353                }
2354            }
2355            needToRedraw = true;
2356            checkSelectionChanged();
2357        }
2358
2359        if (amountToScroll > 0) {
2360            scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2361            needToRedraw = true;
2362        }
2363
2364        // if we didn't find a new focusable, make sure any existing focused
2365        // item that was panned off screen gives up focus.
2366        if (mItemsCanFocus && (focusResult == null)
2367                && selectedView != null && selectedView.hasFocus()) {
2368            final View focused = selectedView.findFocus();
2369            if (distanceToView(focused) > 0) {
2370                focused.clearFocus();
2371            }
2372        }
2373
2374        // if  the current selection is panned off, we need to remove the selection
2375        if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2376                && !isViewAncestorOf(selectedView, this)) {
2377            selectedView = null;
2378            hideSelector();
2379
2380            // but we don't want to set the ressurect position (that would make subsequent
2381            // unhandled key events bring back the item we just scrolled off!)
2382            mResurrectToPosition = INVALID_POSITION;
2383        }
2384
2385        if (needToRedraw) {
2386            if (selectedView != null) {
2387                positionSelector(selectedPos, selectedView);
2388                mSelectedTop = selectedView.getTop();
2389            }
2390            if (!awakenScrollBars()) {
2391                invalidate();
2392            }
2393            invokeOnItemScrollListener();
2394            return true;
2395        }
2396
2397        return false;
2398    }
2399
2400    /**
2401     * When selection changes, it is possible that the previously selected or the
2402     * next selected item will change its size.  If so, we need to offset some folks,
2403     * and re-layout the items as appropriate.
2404     *
2405     * @param selectedView The currently selected view (before changing selection).
2406     *   should be <code>null</code> if there was no previous selection.
2407     * @param direction Either {@link android.view.View#FOCUS_UP} or
2408     *        {@link android.view.View#FOCUS_DOWN}.
2409     * @param newSelectedPosition The position of the next selection.
2410     * @param newFocusAssigned whether new focus was assigned.  This matters because
2411     *        when something has focus, we don't want to show selection (ugh).
2412     */
2413    private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2414            boolean newFocusAssigned) {
2415        if (newSelectedPosition == INVALID_POSITION) {
2416            throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2417        }
2418
2419        // whether or not we are moving down or up, we want to preserve the
2420        // top of whatever view is on top:
2421        // - moving down: the view that had selection
2422        // - moving up: the view that is getting selection
2423        View topView;
2424        View bottomView;
2425        int topViewIndex, bottomViewIndex;
2426        boolean topSelected = false;
2427        final int selectedIndex = mSelectedPosition - mFirstPosition;
2428        final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2429        if (direction == View.FOCUS_UP) {
2430            topViewIndex = nextSelectedIndex;
2431            bottomViewIndex = selectedIndex;
2432            topView = getChildAt(topViewIndex);
2433            bottomView = selectedView;
2434            topSelected = true;
2435        } else {
2436            topViewIndex = selectedIndex;
2437            bottomViewIndex = nextSelectedIndex;
2438            topView = selectedView;
2439            bottomView = getChildAt(bottomViewIndex);
2440        }
2441
2442        final int numChildren = getChildCount();
2443
2444        // start with top view: is it changing size?
2445        if (topView != null) {
2446            topView.setSelected(!newFocusAssigned && topSelected);
2447            measureAndAdjustDown(topView, topViewIndex, numChildren);
2448        }
2449
2450        // is the bottom view changing size?
2451        if (bottomView != null) {
2452            bottomView.setSelected(!newFocusAssigned && !topSelected);
2453            measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2454        }
2455    }
2456
2457    /**
2458     * Re-measure a child, and if its height changes, lay it out preserving its
2459     * top, and adjust the children below it appropriately.
2460     * @param child The child
2461     * @param childIndex The view group index of the child.
2462     * @param numChildren The number of children in the view group.
2463     */
2464    private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2465        int oldHeight = child.getHeight();
2466        measureItem(child);
2467        if (child.getMeasuredHeight() != oldHeight) {
2468            // lay out the view, preserving its top
2469            relayoutMeasuredItem(child);
2470
2471            // adjust views below appropriately
2472            final int heightDelta = child.getMeasuredHeight() - oldHeight;
2473            for (int i = childIndex + 1; i < numChildren; i++) {
2474                getChildAt(i).offsetTopAndBottom(heightDelta);
2475            }
2476        }
2477    }
2478
2479    /**
2480     * Measure a particular list child.
2481     * TODO: unify with setUpChild.
2482     * @param child The child.
2483     */
2484    private void measureItem(View child) {
2485        ViewGroup.LayoutParams p = child.getLayoutParams();
2486        if (p == null) {
2487            p = new ViewGroup.LayoutParams(
2488                    ViewGroup.LayoutParams.MATCH_PARENT,
2489                    ViewGroup.LayoutParams.WRAP_CONTENT);
2490        }
2491
2492        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2493                mListPadding.left + mListPadding.right, p.width);
2494        int lpHeight = p.height;
2495        int childHeightSpec;
2496        if (lpHeight > 0) {
2497            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2498        } else {
2499            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2500        }
2501        child.measure(childWidthSpec, childHeightSpec);
2502    }
2503
2504    /**
2505     * Layout a child that has been measured, preserving its top position.
2506     * TODO: unify with setUpChild.
2507     * @param child The child.
2508     */
2509    private void relayoutMeasuredItem(View child) {
2510        final int w = child.getMeasuredWidth();
2511        final int h = child.getMeasuredHeight();
2512        final int childLeft = mListPadding.left;
2513        final int childRight = childLeft + w;
2514        final int childTop = child.getTop();
2515        final int childBottom = childTop + h;
2516        child.layout(childLeft, childTop, childRight, childBottom);
2517    }
2518
2519    /**
2520     * @return The amount to preview next items when arrow srolling.
2521     */
2522    private int getArrowScrollPreviewLength() {
2523        return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2524    }
2525
2526    /**
2527     * Determine how much we need to scroll in order to get the next selected view
2528     * visible, with a fading edge showing below as applicable.  The amount is
2529     * capped at {@link #getMaxScrollAmount()} .
2530     *
2531     * @param direction either {@link android.view.View#FOCUS_UP} or
2532     *        {@link android.view.View#FOCUS_DOWN}.
2533     * @param nextSelectedPosition The position of the next selection, or
2534     *        {@link #INVALID_POSITION} if there is no next selectable position
2535     * @return The amount to scroll. Note: this is always positive!  Direction
2536     *         needs to be taken into account when actually scrolling.
2537     */
2538    private int amountToScroll(int direction, int nextSelectedPosition) {
2539        final int listBottom = getHeight() - mListPadding.bottom;
2540        final int listTop = mListPadding.top;
2541
2542        final int numChildren = getChildCount();
2543
2544        if (direction == View.FOCUS_DOWN) {
2545            int indexToMakeVisible = numChildren - 1;
2546            if (nextSelectedPosition != INVALID_POSITION) {
2547                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2548            }
2549
2550            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2551            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2552
2553            int goalBottom = listBottom;
2554            if (positionToMakeVisible < mItemCount - 1) {
2555                goalBottom -= getArrowScrollPreviewLength();
2556            }
2557
2558            if (viewToMakeVisible.getBottom() <= goalBottom) {
2559                // item is fully visible.
2560                return 0;
2561            }
2562
2563            if (nextSelectedPosition != INVALID_POSITION
2564                    && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2565                // item already has enough of it visible, changing selection is good enough
2566                return 0;
2567            }
2568
2569            int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2570
2571            if ((mFirstPosition + numChildren) == mItemCount) {
2572                // last is last in list -> make sure we don't scroll past it
2573                final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2574                amountToScroll = Math.min(amountToScroll, max);
2575            }
2576
2577            return Math.min(amountToScroll, getMaxScrollAmount());
2578        } else {
2579            int indexToMakeVisible = 0;
2580            if (nextSelectedPosition != INVALID_POSITION) {
2581                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2582            }
2583            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2584            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2585            int goalTop = listTop;
2586            if (positionToMakeVisible > 0) {
2587                goalTop += getArrowScrollPreviewLength();
2588            }
2589            if (viewToMakeVisible.getTop() >= goalTop) {
2590                // item is fully visible.
2591                return 0;
2592            }
2593
2594            if (nextSelectedPosition != INVALID_POSITION &&
2595                    (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2596                // item already has enough of it visible, changing selection is good enough
2597                return 0;
2598            }
2599
2600            int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2601            if (mFirstPosition == 0) {
2602                // first is first in list -> make sure we don't scroll past it
2603                final int max = listTop - getChildAt(0).getTop();
2604                amountToScroll = Math.min(amountToScroll,  max);
2605            }
2606            return Math.min(amountToScroll, getMaxScrollAmount());
2607        }
2608    }
2609
2610    /**
2611     * Holds results of focus aware arrow scrolling.
2612     */
2613    static private class ArrowScrollFocusResult {
2614        private int mSelectedPosition;
2615        private int mAmountToScroll;
2616
2617        /**
2618         * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2619         */
2620        void populate(int selectedPosition, int amountToScroll) {
2621            mSelectedPosition = selectedPosition;
2622            mAmountToScroll = amountToScroll;
2623        }
2624
2625        public int getSelectedPosition() {
2626            return mSelectedPosition;
2627        }
2628
2629        public int getAmountToScroll() {
2630            return mAmountToScroll;
2631        }
2632    }
2633
2634    /**
2635     * @param direction either {@link android.view.View#FOCUS_UP} or
2636     *        {@link android.view.View#FOCUS_DOWN}.
2637     * @return The position of the next selectable position of the views that
2638     *         are currently visible, taking into account the fact that there might
2639     *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
2640     *         selectable view on screen in the given direction.
2641     */
2642    private int lookForSelectablePositionOnScreen(int direction) {
2643        final int firstPosition = mFirstPosition;
2644        if (direction == View.FOCUS_DOWN) {
2645            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2646                    mSelectedPosition + 1 :
2647                    firstPosition;
2648            if (startPos >= mAdapter.getCount()) {
2649                return INVALID_POSITION;
2650            }
2651            if (startPos < firstPosition) {
2652                startPos = firstPosition;
2653            }
2654
2655            final int lastVisiblePos = getLastVisiblePosition();
2656            final ListAdapter adapter = getAdapter();
2657            for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2658                if (adapter.isEnabled(pos)
2659                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2660                    return pos;
2661                }
2662            }
2663        } else {
2664            int last = firstPosition + getChildCount() - 1;
2665            int startPos = (mSelectedPosition != INVALID_POSITION) ?
2666                    mSelectedPosition - 1 :
2667                    firstPosition + getChildCount() - 1;
2668            if (startPos < 0) {
2669                return INVALID_POSITION;
2670            }
2671            if (startPos > last) {
2672                startPos = last;
2673            }
2674
2675            final ListAdapter adapter = getAdapter();
2676            for (int pos = startPos; pos >= firstPosition; pos--) {
2677                if (adapter.isEnabled(pos)
2678                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2679                    return pos;
2680                }
2681            }
2682        }
2683        return INVALID_POSITION;
2684    }
2685
2686    /**
2687     * Do an arrow scroll based on focus searching.  If a new view is
2688     * given focus, return the selection delta and amount to scroll via
2689     * an {@link ArrowScrollFocusResult}, otherwise, return null.
2690     *
2691     * @param direction either {@link android.view.View#FOCUS_UP} or
2692     *        {@link android.view.View#FOCUS_DOWN}.
2693     * @return The result if focus has changed, or <code>null</code>.
2694     */
2695    private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2696        final View selectedView = getSelectedView();
2697        View newFocus;
2698        if (selectedView != null && selectedView.hasFocus()) {
2699            View oldFocus = selectedView.findFocus();
2700            newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2701        } else {
2702            if (direction == View.FOCUS_DOWN) {
2703                final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2704                final int listTop = mListPadding.top +
2705                        (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2706                final int ySearchPoint =
2707                        (selectedView != null && selectedView.getTop() > listTop) ?
2708                                selectedView.getTop() :
2709                                listTop;
2710                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2711            } else {
2712                final boolean bottomFadingEdgeShowing =
2713                        (mFirstPosition + getChildCount() - 1) < mItemCount;
2714                final int listBottom = getHeight() - mListPadding.bottom -
2715                        (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2716                final int ySearchPoint =
2717                        (selectedView != null && selectedView.getBottom() < listBottom) ?
2718                                selectedView.getBottom() :
2719                                listBottom;
2720                mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2721            }
2722            newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2723        }
2724
2725        if (newFocus != null) {
2726            final int positionOfNewFocus = positionOfNewFocus(newFocus);
2727
2728            // if the focus change is in a different new position, make sure
2729            // we aren't jumping over another selectable position
2730            if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2731                final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2732                if (selectablePosition != INVALID_POSITION &&
2733                        ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2734                        (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2735                    return null;
2736                }
2737            }
2738
2739            int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2740
2741            final int maxScrollAmount = getMaxScrollAmount();
2742            if (focusScroll < maxScrollAmount) {
2743                // not moving too far, safe to give next view focus
2744                newFocus.requestFocus(direction);
2745                mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2746                return mArrowScrollFocusResult;
2747            } else if (distanceToView(newFocus) < maxScrollAmount){
2748                // Case to consider:
2749                // too far to get entire next focusable on screen, but by going
2750                // max scroll amount, we are getting it at least partially in view,
2751                // so give it focus and scroll the max ammount.
2752                newFocus.requestFocus(direction);
2753                mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2754                return mArrowScrollFocusResult;
2755            }
2756        }
2757        return null;
2758    }
2759
2760    /**
2761     * @param newFocus The view that would have focus.
2762     * @return the position that contains newFocus
2763     */
2764    private int positionOfNewFocus(View newFocus) {
2765        final int numChildren = getChildCount();
2766        for (int i = 0; i < numChildren; i++) {
2767            final View child = getChildAt(i);
2768            if (isViewAncestorOf(newFocus, child)) {
2769                return mFirstPosition + i;
2770            }
2771        }
2772        throw new IllegalArgumentException("newFocus is not a child of any of the"
2773                + " children of the list!");
2774    }
2775
2776    /**
2777     * Return true if child is an ancestor of parent, (or equal to the parent).
2778     */
2779    private boolean isViewAncestorOf(View child, View parent) {
2780        if (child == parent) {
2781            return true;
2782        }
2783
2784        final ViewParent theParent = child.getParent();
2785        return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2786    }
2787
2788    /**
2789     * Determine how much we need to scroll in order to get newFocus in view.
2790     * @param direction either {@link android.view.View#FOCUS_UP} or
2791     *        {@link android.view.View#FOCUS_DOWN}.
2792     * @param newFocus The view that would take focus.
2793     * @param positionOfNewFocus The position of the list item containing newFocus
2794     * @return The amount to scroll.  Note: this is always positive!  Direction
2795     *   needs to be taken into account when actually scrolling.
2796     */
2797    private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2798        int amountToScroll = 0;
2799        newFocus.getDrawingRect(mTempRect);
2800        offsetDescendantRectToMyCoords(newFocus, mTempRect);
2801        if (direction == View.FOCUS_UP) {
2802            if (mTempRect.top < mListPadding.top) {
2803                amountToScroll = mListPadding.top - mTempRect.top;
2804                if (positionOfNewFocus > 0) {
2805                    amountToScroll += getArrowScrollPreviewLength();
2806                }
2807            }
2808        } else {
2809            final int listBottom = getHeight() - mListPadding.bottom;
2810            if (mTempRect.bottom > listBottom) {
2811                amountToScroll = mTempRect.bottom - listBottom;
2812                if (positionOfNewFocus < mItemCount - 1) {
2813                    amountToScroll += getArrowScrollPreviewLength();
2814                }
2815            }
2816        }
2817        return amountToScroll;
2818    }
2819
2820    /**
2821     * Determine the distance to the nearest edge of a view in a particular
2822     * direction.
2823     *
2824     * @param descendant A descendant of this list.
2825     * @return The distance, or 0 if the nearest edge is already on screen.
2826     */
2827    private int distanceToView(View descendant) {
2828        int distance = 0;
2829        descendant.getDrawingRect(mTempRect);
2830        offsetDescendantRectToMyCoords(descendant, mTempRect);
2831        final int listBottom = mBottom - mTop - mListPadding.bottom;
2832        if (mTempRect.bottom < mListPadding.top) {
2833            distance = mListPadding.top - mTempRect.bottom;
2834        } else if (mTempRect.top > listBottom) {
2835            distance = mTempRect.top - listBottom;
2836        }
2837        return distance;
2838    }
2839
2840
2841    /**
2842     * Scroll the children by amount, adding a view at the end and removing
2843     * views that fall off as necessary.
2844     *
2845     * @param amount The amount (positive or negative) to scroll.
2846     */
2847    private void scrollListItemsBy(int amount) {
2848        offsetChildrenTopAndBottom(amount);
2849
2850        final int listBottom = getHeight() - mListPadding.bottom;
2851        final int listTop = mListPadding.top;
2852        final AbsListView.RecycleBin recycleBin = mRecycler;
2853
2854        if (amount < 0) {
2855            // shifted items up
2856
2857            // may need to pan views into the bottom space
2858            int numChildren = getChildCount();
2859            View last = getChildAt(numChildren - 1);
2860            while (last.getBottom() < listBottom) {
2861                final int lastVisiblePosition = mFirstPosition + numChildren - 1;
2862                if (lastVisiblePosition < mItemCount - 1) {
2863                    last = addViewBelow(last, lastVisiblePosition);
2864                    numChildren++;
2865                } else {
2866                    break;
2867                }
2868            }
2869
2870            // may have brought in the last child of the list that is skinnier
2871            // than the fading edge, thereby leaving space at the end.  need
2872            // to shift back
2873            if (last.getBottom() < listBottom) {
2874                offsetChildrenTopAndBottom(listBottom - last.getBottom());
2875            }
2876
2877            // top views may be panned off screen
2878            View first = getChildAt(0);
2879            while (first.getBottom() < listTop) {
2880                AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
2881                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
2882                    detachViewFromParent(first);
2883                    recycleBin.addScrapView(first, mFirstPosition);
2884                } else {
2885                    removeViewInLayout(first);
2886                }
2887                first = getChildAt(0);
2888                mFirstPosition++;
2889            }
2890        } else {
2891            // shifted items down
2892            View first = getChildAt(0);
2893
2894            // may need to pan views into top
2895            while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
2896                first = addViewAbove(first, mFirstPosition);
2897                mFirstPosition--;
2898            }
2899
2900            // may have brought the very first child of the list in too far and
2901            // need to shift it back
2902            if (first.getTop() > listTop) {
2903                offsetChildrenTopAndBottom(listTop - first.getTop());
2904            }
2905
2906            int lastIndex = getChildCount() - 1;
2907            View last = getChildAt(lastIndex);
2908
2909            // bottom view may be panned off screen
2910            while (last.getTop() > listBottom) {
2911                AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
2912                if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
2913                    detachViewFromParent(last);
2914                    recycleBin.addScrapView(last, mFirstPosition+lastIndex);
2915                } else {
2916                    removeViewInLayout(last);
2917                }
2918                last = getChildAt(--lastIndex);
2919            }
2920        }
2921    }
2922
2923    private View addViewAbove(View theView, int position) {
2924        int abovePosition = position - 1;
2925        View view = obtainView(abovePosition, mIsScrap);
2926        int edgeOfNewChild = theView.getTop() - mDividerHeight;
2927        setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
2928                false, mIsScrap[0]);
2929        return view;
2930    }
2931
2932    private View addViewBelow(View theView, int position) {
2933        int belowPosition = position + 1;
2934        View view = obtainView(belowPosition, mIsScrap);
2935        int edgeOfNewChild = theView.getBottom() + mDividerHeight;
2936        setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
2937                false, mIsScrap[0]);
2938        return view;
2939    }
2940
2941    /**
2942     * Indicates that the views created by the ListAdapter can contain focusable
2943     * items.
2944     *
2945     * @param itemsCanFocus true if items can get focus, false otherwise
2946     */
2947    public void setItemsCanFocus(boolean itemsCanFocus) {
2948        mItemsCanFocus = itemsCanFocus;
2949        if (!itemsCanFocus) {
2950            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
2951        }
2952    }
2953
2954    /**
2955     * @return Whether the views created by the ListAdapter can contain focusable
2956     * items.
2957     */
2958    public boolean getItemsCanFocus() {
2959        return mItemsCanFocus;
2960    }
2961
2962    /**
2963     * @hide Pending API council approval.
2964     */
2965    @Override
2966    public boolean isOpaque() {
2967        return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque &&
2968                hasOpaqueScrollbars()) || super.isOpaque();
2969    }
2970
2971    @Override
2972    public void setCacheColorHint(int color) {
2973        final boolean opaque = (color >>> 24) == 0xFF;
2974        mIsCacheColorOpaque = opaque;
2975        if (opaque) {
2976            if (mDividerPaint == null) {
2977                mDividerPaint = new Paint();
2978            }
2979            mDividerPaint.setColor(color);
2980        }
2981        super.setCacheColorHint(color);
2982    }
2983
2984    void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
2985        final int height = drawable.getMinimumHeight();
2986
2987        canvas.save();
2988        canvas.clipRect(bounds);
2989
2990        final int span = bounds.bottom - bounds.top;
2991        if (span < height) {
2992            bounds.top = bounds.bottom - height;
2993        }
2994
2995        drawable.setBounds(bounds);
2996        drawable.draw(canvas);
2997
2998        canvas.restore();
2999    }
3000
3001    void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3002        final int height = drawable.getMinimumHeight();
3003
3004        canvas.save();
3005        canvas.clipRect(bounds);
3006
3007        final int span = bounds.bottom - bounds.top;
3008        if (span < height) {
3009            bounds.bottom = bounds.top + height;
3010        }
3011
3012        drawable.setBounds(bounds);
3013        drawable.draw(canvas);
3014
3015        canvas.restore();
3016    }
3017
3018    @Override
3019    protected void dispatchDraw(Canvas canvas) {
3020        // Draw the dividers
3021        final int dividerHeight = mDividerHeight;
3022        final Drawable overscrollHeader = mOverScrollHeader;
3023        final Drawable overscrollFooter = mOverScrollFooter;
3024        final boolean drawOverscrollHeader = overscrollHeader != null;
3025        final boolean drawOverscrollFooter = overscrollFooter != null;
3026        final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3027
3028        if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
3029            // Only modify the top and bottom in the loop, we set the left and right here
3030            final Rect bounds = mTempRect;
3031            bounds.left = mPaddingLeft;
3032            bounds.right = mRight - mLeft - mPaddingRight;
3033
3034            final int count = getChildCount();
3035            final int headerCount = mHeaderViewInfos.size();
3036            final int itemCount = mItemCount;
3037            final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
3038            final boolean headerDividers = mHeaderDividersEnabled;
3039            final boolean footerDividers = mFooterDividersEnabled;
3040            final int first = mFirstPosition;
3041            final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3042            final ListAdapter adapter = mAdapter;
3043            // If the list is opaque *and* the background is not, we want to
3044            // fill a rect where the dividers would be for non-selectable items
3045            // If the list is opaque and the background is also opaque, we don't
3046            // need to draw anything since the background will do it for us
3047            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3048
3049            if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
3050                mDividerPaint = new Paint();
3051                mDividerPaint.setColor(getCacheColorHint());
3052            }
3053            final Paint paint = mDividerPaint;
3054
3055            final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
3056            if (!mStackFromBottom) {
3057                int bottom = 0;
3058
3059                // Draw top divider or header for overscroll
3060                final int scrollY = mScrollY;
3061                if (count > 0 && scrollY < 0) {
3062                    if (drawOverscrollHeader) {
3063                        bounds.bottom = 0;
3064                        bounds.top = scrollY;
3065                        drawOverscrollHeader(canvas, overscrollHeader, bounds);
3066                    } else if (drawDividers) {
3067                        bounds.bottom = 0;
3068                        bounds.top = -dividerHeight;
3069                        drawDivider(canvas, bounds, -1);
3070                    }
3071                }
3072
3073                for (int i = 0; i < count; i++) {
3074                    if ((headerDividers || first + i >= headerCount) &&
3075                            (footerDividers || first + i < footerLimit)) {
3076                        View child = getChildAt(i);
3077                        bottom = child.getBottom();
3078                        // Don't draw dividers next to items that are not enabled
3079
3080                        if (drawDividers &&
3081                                (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
3082                            if ((areAllItemsSelectable ||
3083                                    (adapter.isEnabled(first + i) && (i == count - 1 ||
3084                                            adapter.isEnabled(first + i + 1))))) {
3085                                bounds.top = bottom;
3086                                bounds.bottom = bottom + dividerHeight;
3087                                drawDivider(canvas, bounds, i);
3088                            } else if (fillForMissingDividers) {
3089                                bounds.top = bottom;
3090                                bounds.bottom = bottom + dividerHeight;
3091                                canvas.drawRect(bounds, paint);
3092                            }
3093                        }
3094                    }
3095                }
3096
3097                final int overFooterBottom = mBottom + mScrollY;
3098                if (drawOverscrollFooter && first + count == itemCount &&
3099                        overFooterBottom > bottom) {
3100                    bounds.top = bottom;
3101                    bounds.bottom = overFooterBottom;
3102                    drawOverscrollFooter(canvas, overscrollFooter, bounds);
3103                }
3104            } else {
3105                int top;
3106                int listTop = mListPadding.top;
3107
3108                final int scrollY = mScrollY;
3109
3110                if (count > 0 && drawOverscrollHeader) {
3111                    bounds.top = scrollY;
3112                    bounds.bottom = getChildAt(0).getTop();
3113                    drawOverscrollHeader(canvas, overscrollHeader, bounds);
3114                }
3115
3116                final int start = drawOverscrollHeader ? 1 : 0;
3117                for (int i = start; i < count; i++) {
3118                    if ((headerDividers || first + i >= headerCount) &&
3119                            (footerDividers || first + i < footerLimit)) {
3120                        View child = getChildAt(i);
3121                        top = child.getTop();
3122                        // Don't draw dividers next to items that are not enabled
3123                        if (top > listTop) {
3124                            if ((areAllItemsSelectable ||
3125                                    (adapter.isEnabled(first + i) && (i == count - 1 ||
3126                                            adapter.isEnabled(first + i + 1))))) {
3127                                bounds.top = top - dividerHeight;
3128                                bounds.bottom = top;
3129                                // Give the method the child ABOVE the divider, so we
3130                                // subtract one from our child
3131                                // position. Give -1 when there is no child above the
3132                                // divider.
3133                                drawDivider(canvas, bounds, i - 1);
3134                            } else if (fillForMissingDividers) {
3135                                bounds.top = top - dividerHeight;
3136                                bounds.bottom = top;
3137                                canvas.drawRect(bounds, paint);
3138                            }
3139                        }
3140                    }
3141                }
3142
3143                if (count > 0 && scrollY > 0) {
3144                    if (drawOverscrollFooter) {
3145                        final int absListBottom = mBottom;
3146                        bounds.top = absListBottom;
3147                        bounds.bottom = absListBottom + scrollY;
3148                        drawOverscrollFooter(canvas, overscrollFooter, bounds);
3149                    } else if (drawDividers) {
3150                        bounds.top = listBottom;
3151                        bounds.bottom = listBottom + dividerHeight;
3152                        drawDivider(canvas, bounds, -1);
3153                    }
3154                }
3155            }
3156        }
3157
3158        // Draw the indicators (these should be drawn above the dividers) and children
3159        super.dispatchDraw(canvas);
3160    }
3161
3162    /**
3163     * Draws a divider for the given child in the given bounds.
3164     *
3165     * @param canvas The canvas to draw to.
3166     * @param bounds The bounds of the divider.
3167     * @param childIndex The index of child (of the View) above the divider.
3168     *            This will be -1 if there is no child above the divider to be
3169     *            drawn.
3170     */
3171    void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3172        // This widget draws the same divider for all children
3173        final Drawable divider = mDivider;
3174
3175        divider.setBounds(bounds);
3176        divider.draw(canvas);
3177    }
3178
3179    /**
3180     * Returns the drawable that will be drawn between each item in the list.
3181     *
3182     * @return the current drawable drawn between list elements
3183     */
3184    public Drawable getDivider() {
3185        return mDivider;
3186    }
3187
3188    /**
3189     * Sets the drawable that will be drawn between each item in the list. If the drawable does
3190     * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3191     *
3192     * @param divider The drawable to use.
3193     */
3194    public void setDivider(Drawable divider) {
3195        if (divider != null) {
3196            mDividerHeight = divider.getIntrinsicHeight();
3197        } else {
3198            mDividerHeight = 0;
3199        }
3200        mDivider = divider;
3201        mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
3202        requestLayout();
3203        invalidate();
3204    }
3205
3206    /**
3207     * @return Returns the height of the divider that will be drawn between each item in the list.
3208     */
3209    public int getDividerHeight() {
3210        return mDividerHeight;
3211    }
3212
3213    /**
3214     * Sets the height of the divider that will be drawn between each item in the list. Calling
3215     * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3216     *
3217     * @param height The new height of the divider in pixels.
3218     */
3219    public void setDividerHeight(int height) {
3220        mDividerHeight = height;
3221        requestLayout();
3222        invalidate();
3223    }
3224
3225    /**
3226     * Enables or disables the drawing of the divider for header views.
3227     *
3228     * @param headerDividersEnabled True to draw the headers, false otherwise.
3229     *
3230     * @see #setFooterDividersEnabled(boolean)
3231     * @see #addHeaderView(android.view.View)
3232     */
3233    public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3234        mHeaderDividersEnabled = headerDividersEnabled;
3235        invalidate();
3236    }
3237
3238    /**
3239     * Enables or disables the drawing of the divider for footer views.
3240     *
3241     * @param footerDividersEnabled True to draw the footers, false otherwise.
3242     *
3243     * @see #setHeaderDividersEnabled(boolean)
3244     * @see #addFooterView(android.view.View)
3245     */
3246    public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3247        mFooterDividersEnabled = footerDividersEnabled;
3248        invalidate();
3249    }
3250
3251    /**
3252     * Sets the drawable that will be drawn above all other list content.
3253     * This area can become visible when the user overscrolls the list.
3254     *
3255     * @param header The drawable to use
3256     */
3257    public void setOverscrollHeader(Drawable header) {
3258        mOverScrollHeader = header;
3259        if (mScrollY < 0) {
3260            invalidate();
3261        }
3262    }
3263
3264    /**
3265     * @return The drawable that will be drawn above all other list content
3266     */
3267    public Drawable getOverscrollHeader() {
3268        return mOverScrollHeader;
3269    }
3270
3271    /**
3272     * Sets the drawable that will be drawn below all other list content.
3273     * This area can become visible when the user overscrolls the list,
3274     * or when the list's content does not fully fill the container area.
3275     *
3276     * @param footer The drawable to use
3277     */
3278    public void setOverscrollFooter(Drawable footer) {
3279        mOverScrollFooter = footer;
3280        invalidate();
3281    }
3282
3283    /**
3284     * @return The drawable that will be drawn below all other list content
3285     */
3286    public Drawable getOverscrollFooter() {
3287        return mOverScrollFooter;
3288    }
3289
3290    @Override
3291    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3292        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3293
3294        int closetChildIndex = -1;
3295        if (gainFocus && previouslyFocusedRect != null) {
3296            previouslyFocusedRect.offset(mScrollX, mScrollY);
3297
3298            final ListAdapter adapter = mAdapter;
3299            // Don't cache the result of getChildCount or mFirstPosition here,
3300            // it could change in layoutChildren.
3301            if (adapter.getCount() < getChildCount() + mFirstPosition) {
3302                mLayoutMode = LAYOUT_NORMAL;
3303                layoutChildren();
3304            }
3305
3306            // figure out which item should be selected based on previously
3307            // focused rect
3308            Rect otherRect = mTempRect;
3309            int minDistance = Integer.MAX_VALUE;
3310            final int childCount = getChildCount();
3311            final int firstPosition = mFirstPosition;
3312
3313            for (int i = 0; i < childCount; i++) {
3314                // only consider selectable views
3315                if (!adapter.isEnabled(firstPosition + i)) {
3316                    continue;
3317                }
3318
3319                View other = getChildAt(i);
3320                other.getDrawingRect(otherRect);
3321                offsetDescendantRectToMyCoords(other, otherRect);
3322                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3323
3324                if (distance < minDistance) {
3325                    minDistance = distance;
3326                    closetChildIndex = i;
3327                }
3328            }
3329        }
3330
3331        if (closetChildIndex >= 0) {
3332            setSelection(closetChildIndex + mFirstPosition);
3333        } else {
3334            requestLayout();
3335        }
3336    }
3337
3338
3339    /*
3340     * (non-Javadoc)
3341     *
3342     * Children specified in XML are assumed to be header views. After we have
3343     * parsed them move them out of the children list and into mHeaderViews.
3344     */
3345    @Override
3346    protected void onFinishInflate() {
3347        super.onFinishInflate();
3348
3349        int count = getChildCount();
3350        if (count > 0) {
3351            for (int i = 0; i < count; ++i) {
3352                addHeaderView(getChildAt(i));
3353            }
3354            removeAllViews();
3355        }
3356    }
3357
3358    /* (non-Javadoc)
3359     * @see android.view.View#findViewById(int)
3360     * First look in our children, then in any header and footer views that may be scrolled off.
3361     */
3362    @Override
3363    protected View findViewTraversal(int id) {
3364        View v;
3365        v = super.findViewTraversal(id);
3366        if (v == null) {
3367            v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3368            if (v != null) {
3369                return v;
3370            }
3371            v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3372            if (v != null) {
3373                return v;
3374            }
3375        }
3376        return v;
3377    }
3378
3379    /* (non-Javadoc)
3380     *
3381     * Look in the passed in list of headers or footers for the view.
3382     */
3383    View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3384        if (where != null) {
3385            int len = where.size();
3386            View v;
3387
3388            for (int i = 0; i < len; i++) {
3389                v = where.get(i).view;
3390
3391                if (!v.isRootNamespace()) {
3392                    v = v.findViewById(id);
3393
3394                    if (v != null) {
3395                        return v;
3396                    }
3397                }
3398            }
3399        }
3400        return null;
3401    }
3402
3403    /* (non-Javadoc)
3404     * @see android.view.View#findViewWithTag(String)
3405     * First look in our children, then in any header and footer views that may be scrolled off.
3406     */
3407    @Override
3408    protected View findViewWithTagTraversal(Object tag) {
3409        View v;
3410        v = super.findViewWithTagTraversal(tag);
3411        if (v == null) {
3412            v = findViewTagInHeadersOrFooters(mHeaderViewInfos, tag);
3413            if (v != null) {
3414                return v;
3415            }
3416
3417            v = findViewTagInHeadersOrFooters(mFooterViewInfos, tag);
3418            if (v != null) {
3419                return v;
3420            }
3421        }
3422        return v;
3423    }
3424
3425    /* (non-Javadoc)
3426     *
3427     * Look in the passed in list of headers or footers for the view with the tag.
3428     */
3429    View findViewTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
3430        if (where != null) {
3431            int len = where.size();
3432            View v;
3433
3434            for (int i = 0; i < len; i++) {
3435                v = where.get(i).view;
3436
3437                if (!v.isRootNamespace()) {
3438                    v = v.findViewWithTag(tag);
3439
3440                    if (v != null) {
3441                        return v;
3442                    }
3443                }
3444            }
3445        }
3446        return null;
3447    }
3448
3449    @Override
3450    public boolean onTouchEvent(MotionEvent ev) {
3451        if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
3452            // Don't handle edge touches immediately -- they may actually belong to one of our
3453            // descendants.
3454            return false;
3455        }
3456        return super.onTouchEvent(ev);
3457    }
3458
3459    /**
3460     * Returns the set of checked items ids. The result is only valid if the
3461     * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3462     *
3463     * @return A new array which contains the id of each checked item in the
3464     *         list.
3465     *
3466     * @deprecated Use {@link #getCheckedItemIds()} instead.
3467     */
3468    @Deprecated
3469    public long[] getCheckItemIds() {
3470        // Use new behavior that correctly handles stable ID mapping.
3471        if (mAdapter != null && mAdapter.hasStableIds()) {
3472            return getCheckedItemIds();
3473        }
3474
3475        // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3476        // Fall back to it to support legacy apps.
3477        if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3478            final SparseBooleanArray states = mCheckStates;
3479            final int count = states.size();
3480            final long[] ids = new long[count];
3481            final ListAdapter adapter = mAdapter;
3482
3483            int checkedCount = 0;
3484            for (int i = 0; i < count; i++) {
3485                if (states.valueAt(i)) {
3486                    ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3487                }
3488            }
3489
3490            // Trim array if needed. mCheckStates may contain false values
3491            // resulting in checkedCount being smaller than count.
3492            if (checkedCount == count) {
3493                return ids;
3494            } else {
3495                final long[] result = new long[checkedCount];
3496                System.arraycopy(ids, 0, result, 0, checkedCount);
3497
3498                return result;
3499            }
3500        }
3501        return new long[0];
3502    }
3503}
3504