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